summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /pigeonhole/src/lib-sieve
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pigeonhole/src/lib-sieve')
-rw-r--r--pigeonhole/src/lib-sieve/Makefile.am188
-rw-r--r--pigeonhole/src/lib-sieve/Makefile.in1311
-rw-r--r--pigeonhole/src/lib-sieve/cmd-discard.c173
-rw-r--r--pigeonhole/src/lib-sieve/cmd-if.c277
-rw-r--r--pigeonhole/src/lib-sieve/cmd-keep.c113
-rw-r--r--pigeonhole/src/lib-sieve/cmd-redirect.c677
-rw-r--r--pigeonhole/src/lib-sieve/cmd-require.c86
-rw-r--r--pigeonhole/src/lib-sieve/cmd-stop.c86
-rw-r--r--pigeonhole/src/lib-sieve/cmp-i-ascii-casemap.c99
-rw-r--r--pigeonhole/src/lib-sieve/cmp-i-octet.c97
-rw-r--r--pigeonhole/src/lib-sieve/ext-encoded-character.c271
-rw-r--r--pigeonhole/src/lib-sieve/ext-envelope.c732
-rw-r--r--pigeonhole/src/lib-sieve/ext-fileinto.c225
-rw-r--r--pigeonhole/src/lib-sieve/ext-reject.c606
-rw-r--r--pigeonhole/src/lib-sieve/mcht-contains.c66
-rw-r--r--pigeonhole/src/lib-sieve/mcht-is.c52
-rw-r--r--pigeonhole/src/lib-sieve/mcht-matches.c440
-rw-r--r--pigeonhole/src/lib-sieve/plugins/Makefile.am32
-rw-r--r--pigeonhole/src/lib-sieve/plugins/Makefile.in719
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/Makefile.am16
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/Makefile.in692
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/ext-body-common.c102
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/ext-body-common.h40
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/ext-body.c54
-rw-r--r--pigeonhole/src/lib-sieve/plugins/body/tst-body.c385
-rw-r--r--pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.am8
-rw-r--r--pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in674
-rw-r--r--pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c160
-rw-r--r--pigeonhole/src/lib-sieve/plugins/copy/Makefile.am18
-rw-r--r--pigeonhole/src/lib-sieve/plugins/copy/Makefile.in735
-rw-r--r--pigeonhole/src/lib-sieve/plugins/copy/ext-copy.c183
-rw-r--r--pigeonhole/src/lib-sieve/plugins/copy/sieve-ext-copy.h21
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/Makefile.am16
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/Makefile.in692
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/ext-date-common.c593
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/ext-date-common.h80
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/ext-date.c62
-rw-r--r--pigeonhole/src/lib-sieve/plugins/date/tst-date.c496
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.am20
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in697
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c298
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h41
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate.c107
-rw-r--r--pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c449
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/Makefile.am19
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in701
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/cmd-addheader.c337
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.c551
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.c211
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.h48
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-limits.h7
-rw-r--r--pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader.c66
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/Makefile.am44
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in904
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/cmd-notify.c621
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.c718
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.h122
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-limits.h6
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify.c103
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.am16
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in687
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c794
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c683
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.h49
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h197
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c233
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c144
-rw-r--r--pigeonhole/src/lib-sieve/plugins/enotify/vmodf-encodeurl.c119
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/Makefile.am24
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/Makefile.in753
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.c339
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.h53
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/ext-environment.c59
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/sieve-ext-environment.h54
-rw-r--r--pigeonhole/src/lib-sieve/plugins/environment/tst-environment.c215
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/Makefile.am22
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in707
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/cmd-error.c131
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.c249
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.h33
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.c52
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.h52
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave.c70
-rw-r--r--pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c294
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.am33
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in776
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/cmd-flag.c251
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c733
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h97
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags.c96
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imapflags.c213
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/sieve-ext-imap4flags.h74
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/tag-flags.c402
-rw-r--r--pigeonhole/src/lib-sieve/plugins/imap4flags/tst-hasflag.c248
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/Makefile.am24
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/Makefile.in718
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/cmd-global.c329
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/cmd-include.c406
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/cmd-return.c71
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.c492
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.h64
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c892
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-common.h170
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-limits.h9
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.c254
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.h41
-rw-r--r--pigeonhole/src/lib-sieve/plugins/include/ext-include.c121
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/Makefile.am13
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/Makefile.in688
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/ext-index-common.c15
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/ext-index-common.h29
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/ext-index.c69
-rw-r--r--pigeonhole/src/lib-sieve/plugins/index/tag-index.c278
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.am26
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in757
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox-common.h39
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox.c72
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/sieve-ext-mailbox.h21
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c186
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c284
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/Makefile.am24
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in705
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata-common.h40
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata.c83
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c433
-rw-r--r--pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.c431
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/Makefile.am30
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/Makefile.in727
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c273
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/cmd-extracttext.c370
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/cmd-foreverypart.c377
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/ext-extracttext.c130
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/ext-foreverypart.c62
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.c27
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.h85
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/ext-mime.c77
-rw-r--r--pigeonhole/src/lib-sieve/plugins/mime/tag-mime.c757
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/Makefile.am20
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/Makefile.in699
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/cmd-denotify.c389
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c900
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.c361
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.h66
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/ext-notify-limits.h7
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/ext-notify.c108
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/Makefile.am13
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/Makefile.in688
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.c22
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.h24
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/ext-regex.c65
-rw-r--r--pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c385
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/Makefile.am14
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/Makefile.in694
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.c171
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.h86
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/ext-relational.c53
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/mcht-count.c119
-rw-r--r--pigeonhole/src/lib-sieve/plugins/relational/mcht-value.c80
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.am16
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in694
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c674
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.h35
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest.c146
-rw-r--r--pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c304
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/Makefile.am22
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in703
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.c31
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.h43
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use.c57
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/tag-specialuse.c316
-rw-r--r--pigeonhole/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c525
-rw-r--r--pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.am8
-rw-r--r--pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in673
-rw-r--r--pigeonhole/src/lib-sieve/plugins/subaddress/ext-subaddress.c191
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/Makefile.am19
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in700
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c1578
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.c114
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.h60
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-seconds.c66
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation.c131
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/Makefile.am41
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/Makefile.in801
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/cmd-set.c235
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.c420
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.h24
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.c950
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.h102
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.c137
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.h22
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-limits.h35
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.c578
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.h66
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.c110
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.h43
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.c236
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.h43
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.c279
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.h37
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/ext-variables.c84
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/sieve-ext-variables.h364
-rw-r--r--pigeonhole/src/lib-sieve/plugins/variables/tst-string.c271
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.am2
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in692
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.am15
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in687
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c130
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h22
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c70
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am16
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in692
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h37
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c95
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c206
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c112
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.am17
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in695
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c692
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c51
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h40
-rw-r--r--pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c52
-rw-r--r--pigeonhole/src/lib-sieve/sieve-actions.c1096
-rw-r--r--pigeonhole/src/lib-sieve/sieve-actions.h311
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address-parts.c495
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address-parts.h135
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address-source.c119
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address-source.h36
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address.c558
-rw-r--r--pigeonhole/src/lib-sieve/sieve-address.h67
-rw-r--r--pigeonhole/src/lib-sieve/sieve-ast.c1111
-rw-r--r--pigeonhole/src/lib-sieve/sieve-ast.h382
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-code.c405
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-debug.c276
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-dumper.c326
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-dumper.h41
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-file.c1042
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary-private.h240
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary.c606
-rw-r--r--pigeonhole/src/lib-sieve/sieve-binary.h291
-rw-r--r--pigeonhole/src/lib-sieve/sieve-code-dumper.c351
-rw-r--r--pigeonhole/src/lib-sieve/sieve-code-dumper.h55
-rw-r--r--pigeonhole/src/lib-sieve/sieve-code.c1169
-rw-r--r--pigeonhole/src/lib-sieve/sieve-code.h351
-rw-r--r--pigeonhole/src/lib-sieve/sieve-commands.c403
-rw-r--r--pigeonhole/src/lib-sieve/sieve-commands.h286
-rw-r--r--pigeonhole/src/lib-sieve/sieve-common.h240
-rw-r--r--pigeonhole/src/lib-sieve/sieve-comparators.c260
-rw-r--r--pigeonhole/src/lib-sieve/sieve-comparators.h153
-rw-r--r--pigeonhole/src/lib-sieve/sieve-config.h16
-rw-r--r--pigeonhole/src/lib-sieve/sieve-dump.h30
-rw-r--r--pigeonhole/src/lib-sieve/sieve-error-private.h67
-rw-r--r--pigeonhole/src/lib-sieve/sieve-error.c1064
-rw-r--r--pigeonhole/src/lib-sieve/sieve-error.h235
-rw-r--r--pigeonhole/src/lib-sieve/sieve-execute.c162
-rw-r--r--pigeonhole/src/lib-sieve/sieve-execute.h42
-rw-r--r--pigeonhole/src/lib-sieve/sieve-extensions.c879
-rw-r--r--pigeonhole/src/lib-sieve/sieve-extensions.h191
-rw-r--r--pigeonhole/src/lib-sieve/sieve-generator.c578
-rw-r--r--pigeonhole/src/lib-sieve/sieve-generator.h121
-rw-r--r--pigeonhole/src/lib-sieve/sieve-interpreter.c1196
-rw-r--r--pigeonhole/src/lib-sieve/sieve-interpreter.h204
-rw-r--r--pigeonhole/src/lib-sieve/sieve-lexer.c930
-rw-r--r--pigeonhole/src/lib-sieve/sieve-lexer.h119
-rw-r--r--pigeonhole/src/lib-sieve/sieve-limits.h45
-rw-r--r--pigeonhole/src/lib-sieve/sieve-match-types.c569
-rw-r--r--pigeonhole/src/lib-sieve/sieve-match-types.h233
-rw-r--r--pigeonhole/src/lib-sieve/sieve-match.c293
-rw-r--r--pigeonhole/src/lib-sieve/sieve-match.h68
-rw-r--r--pigeonhole/src/lib-sieve/sieve-message.c1845
-rw-r--r--pigeonhole/src/lib-sieve/sieve-message.h280
-rw-r--r--pigeonhole/src/lib-sieve/sieve-objects.c111
-rw-r--r--pigeonhole/src/lib-sieve/sieve-objects.h67
-rw-r--r--pigeonhole/src/lib-sieve/sieve-parser.c670
-rw-r--r--pigeonhole/src/lib-sieve/sieve-parser.h17
-rw-r--r--pigeonhole/src/lib-sieve/sieve-plugins.c181
-rw-r--r--pigeonhole/src/lib-sieve/sieve-plugins.h9
-rw-r--r--pigeonhole/src/lib-sieve/sieve-result.c2445
-rw-r--r--pigeonhole/src/lib-sieve/sieve-result.h224
-rw-r--r--pigeonhole/src/lib-sieve/sieve-runtime-trace.c151
-rw-r--r--pigeonhole/src/lib-sieve/sieve-runtime-trace.h182
-rw-r--r--pigeonhole/src/lib-sieve/sieve-runtime.h40
-rw-r--r--pigeonhole/src/lib-sieve/sieve-script-private.h100
-rw-r--r--pigeonhole/src/lib-sieve/sieve-script.c906
-rw-r--r--pigeonhole/src/lib-sieve/sieve-script.h163
-rw-r--r--pigeonhole/src/lib-sieve/sieve-settings.c270
-rw-r--r--pigeonhole/src/lib-sieve/sieve-settings.h57
-rw-r--r--pigeonhole/src/lib-sieve/sieve-smtp.c95
-rw-r--r--pigeonhole/src/lib-sieve/sieve-smtp.h31
-rw-r--r--pigeonhole/src/lib-sieve/sieve-storage-private.h256
-rw-r--r--pigeonhole/src/lib-sieve/sieve-storage-sync.c195
-rw-r--r--pigeonhole/src/lib-sieve/sieve-storage.c1590
-rw-r--r--pigeonhole/src/lib-sieve/sieve-storage.h187
-rw-r--r--pigeonhole/src/lib-sieve/sieve-stringlist.c276
-rw-r--r--pigeonhole/src/lib-sieve/sieve-stringlist.h74
-rw-r--r--pigeonhole/src/lib-sieve/sieve-types.h316
-rw-r--r--pigeonhole/src/lib-sieve/sieve-validator.c1706
-rw-r--r--pigeonhole/src/lib-sieve/sieve-validator.h197
-rw-r--r--pigeonhole/src/lib-sieve/sieve.c1303
-rw-r--r--pigeonhole/src/lib-sieve/sieve.h261
-rw-r--r--pigeonhole/src/lib-sieve/storage/Makefile.am5
-rw-r--r--pigeonhole/src/lib-sieve/storage/Makefile.in697
-rw-r--r--pigeonhole/src/lib-sieve/storage/data/Makefile.am13
-rw-r--r--pigeonhole/src/lib-sieve/storage/data/Makefile.in686
-rw-r--r--pigeonhole/src/lib-sieve/storage/data/sieve-data-script.c95
-rw-r--r--pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.c47
-rw-r--r--pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.h30
-rw-r--r--pigeonhole/src/lib-sieve/storage/dict/Makefile.am13
-rw-r--r--pigeonhole/src/lib-sieve/storage/dict/Makefile.in686
-rw-r--r--pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c343
-rw-r--r--pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c193
-rw-r--r--pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.h66
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/Makefile.am19
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/Makefile.in714
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-script-sequence.c244
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c832
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-active.c403
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-list.c140
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-quota.c120
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-save.c544
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c918
-rw-r--r--pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h186
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/Makefile.am32
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/Makefile.in843
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.c1378
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.h140
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-script.c369
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage-settings.c169
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.c230
-rw-r--r--pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.h108
-rw-r--r--pigeonhole/src/lib-sieve/tst-address.c280
-rw-r--r--pigeonhole/src/lib-sieve/tst-allof.c108
-rw-r--r--pigeonhole/src/lib-sieve/tst-anyof.c107
-rw-r--r--pigeonhole/src/lib-sieve/tst-exists.c179
-rw-r--r--pigeonhole/src/lib-sieve/tst-header.c204
-rw-r--r--pigeonhole/src/lib-sieve/tst-not.c67
-rw-r--r--pigeonhole/src/lib-sieve/tst-size.c318
-rw-r--r--pigeonhole/src/lib-sieve/tst-truefalse.c104
-rw-r--r--pigeonhole/src/lib-sieve/util/Makefile.am51
-rw-r--r--pigeonhole/src/lib-sieve/util/Makefile.in810
-rw-r--r--pigeonhole/src/lib-sieve/util/edit-mail.c2254
-rw-r--r--pigeonhole/src/lib-sieve/util/edit-mail.h47
-rw-r--r--pigeonhole/src/lib-sieve/util/mail-raw.c247
-rw-r--r--pigeonhole/src/lib-sieve/util/mail-raw.h27
-rw-r--r--pigeonhole/src/lib-sieve/util/rfc2822.c277
-rw-r--r--pigeonhole/src/lib-sieve/util/rfc2822.h46
-rw-r--r--pigeonhole/src/lib-sieve/util/test-edit-mail.c842
-rw-r--r--pigeonhole/src/lib-sieve/util/test-rfc2822.c197
347 files changed, 106749 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/Makefile.am b/pigeonhole/src/lib-sieve/Makefile.am
new file mode 100644
index 0000000..2e80871
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/Makefile.am
@@ -0,0 +1,188 @@
+SUBDIRS = util storage plugins
+
+dovecot_pkglib_LTLIBRARIES = libdovecot-sieve.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -I$(top_srcdir)/src/lib-sieve/util \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+tests = \
+ tst-truefalse.c \
+ tst-not.c \
+ tst-anyof.c \
+ tst-allof.c \
+ tst-address.c \
+ tst-header.c \
+ tst-exists.c \
+ tst-size.c
+
+commands = \
+ cmd-require.c \
+ cmd-stop.c \
+ cmd-if.c \
+ cmd-keep.c \
+ cmd-redirect.c \
+ cmd-discard.c
+
+extensions = \
+ ext-fileinto.c \
+ ext-reject.c \
+ ext-envelope.c \
+ ext-encoded-character.c
+
+match_types = \
+ mcht-is.c \
+ mcht-contains.c \
+ mcht-matches.c
+
+comparators = \
+ cmp-i-octet.c \
+ cmp-i-ascii-casemap.c
+
+if BUILD_UNFINISHED
+unfinished_storages =
+unfinished_plugins =
+endif
+
+strgdir = $(top_builddir)/src/lib-sieve/storage
+storages = \
+ $(strgdir)/data/libsieve_storage_data.la \
+ $(strgdir)/file/libsieve_storage_file.la \
+ $(strgdir)/dict/libsieve_storage_dict.la \
+ $(strgdir)/ldap/libsieve_storage_ldap.la \
+ $(unfinished_storages)
+
+extdir = $(top_builddir)/src/lib-sieve/plugins
+plugins = \
+ $(extdir)/vacation/libsieve_ext_vacation.la \
+ $(extdir)/subaddress/libsieve_ext_subaddress.la \
+ $(extdir)/comparator-i-ascii-numeric/libsieve_ext_comparator-i-ascii-numeric.la \
+ $(extdir)/relational/libsieve_ext_relational.la \
+ $(extdir)/regex/libsieve_ext_regex.la \
+ $(extdir)/copy/libsieve_ext_copy.la \
+ $(extdir)/imap4flags/libsieve_ext_imap4flags.la \
+ $(extdir)/include/libsieve_ext_include.la \
+ $(extdir)/body/libsieve_ext_body.la \
+ $(extdir)/variables/libsieve_ext_variables.la \
+ $(extdir)/enotify/libsieve_ext_enotify.la \
+ $(extdir)/notify/libsieve_ext_notify.la \
+ $(extdir)/environment/libsieve_ext_environment.la \
+ $(extdir)/mailbox/libsieve_ext_mailbox.la \
+ $(extdir)/date/libsieve_ext_date.la \
+ $(extdir)/spamvirustest/libsieve_ext_spamvirustest.la \
+ $(extdir)/ihave/libsieve_ext_ihave.la \
+ $(extdir)/editheader/libsieve_ext_editheader.la \
+ $(extdir)/duplicate/libsieve_ext_duplicate.la \
+ $(extdir)/index/libsieve_ext_index.la \
+ $(extdir)/metadata/libsieve_ext_metadata.la \
+ $(extdir)/mime/libsieve_ext_mime.la \
+ $(extdir)/special-use/libsieve_ext_special_use.la \
+ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+ $(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
+ $(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \
+ $(unfinished_plugins)
+
+libdovecot_sieve_la_DEPENDENCIES = \
+ $(storages) \
+ $(plugins) \
+ $(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+libdovecot_sieve_la_LIBADD = \
+ $(storages) \
+ $(plugins) \
+ $(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+libdovecot_sieve_la_SOURCES = \
+ sieve-settings.c \
+ sieve-message.c \
+ sieve-smtp.c \
+ sieve-lexer.c \
+ sieve-script.c \
+ sieve-storage.c \
+ sieve-storage-sync.c \
+ sieve-ast.c \
+ sieve-binary.c \
+ sieve-binary-file.c \
+ sieve-binary-code.c \
+ sieve-binary-debug.c \
+ sieve-parser.c \
+ sieve-address.c \
+ sieve-validator.c \
+ sieve-generator.c \
+ sieve-execute.c \
+ sieve-interpreter.c \
+ sieve-runtime-trace.c \
+ sieve-code-dumper.c \
+ sieve-binary-dumper.c \
+ sieve-result.c \
+ sieve-error.c \
+ sieve-objects.c \
+ sieve-stringlist.c \
+ sieve-comparators.c \
+ sieve-match-types.c \
+ sieve-address-parts.c \
+ sieve-address-source.c \
+ sieve-match.c \
+ sieve-commands.c \
+ sieve-code.c \
+ sieve-actions.c \
+ sieve-extensions.c \
+ sieve-plugins.c \
+ $(comparators) \
+ $(match_types) \
+ $(tests) \
+ $(commands) \
+ $(extensions) \
+ sieve.c
+
+headers = \
+ sieve-config.h \
+ sieve-types.h \
+ sieve-common.h \
+ sieve-limits.h \
+ sieve-settings.h \
+ sieve-message.h \
+ sieve-smtp.h \
+ sieve-lexer.h \
+ sieve-script.h \
+ sieve-script-private.h \
+ sieve-storage.h \
+ sieve-storage-private.h \
+ sieve-ast.h \
+ sieve-binary.h \
+ sieve-binary-private.h \
+ sieve-parser.h \
+ sieve-address.h \
+ sieve-validator.h \
+ sieve-generator.h \
+ sieve-execute.h \
+ sieve-interpreter.h \
+ sieve-runtime-trace.h \
+ sieve-runtime.h \
+ sieve-code-dumper.h \
+ sieve-binary-dumper.h \
+ sieve-dump.h \
+ sieve-result.h \
+ sieve-error.h \
+ sieve-error-private.h \
+ sieve-objects.h \
+ sieve-stringlist.h \
+ sieve-match.h \
+ sieve-comparators.h \
+ sieve-match-types.h \
+ sieve-address-parts.h \
+ sieve-address-source.h \
+ sieve-commands.h \
+ sieve-code.h \
+ sieve-actions.h \
+ sieve-extensions.h \
+ sieve-plugins.h \
+ sieve.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
diff --git a/pigeonhole/src/lib-sieve/Makefile.in b/pigeonhole/src/lib-sieve/Makefile.in
new file mode 100644
index 0000000..104d4df
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/Makefile.in
@@ -0,0 +1,1311 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(dovecot_pkglibdir)" \
+ "$(DESTDIR)$(pkginc_libdir)"
+LTLIBRARIES = $(dovecot_pkglib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(strgdir)/data/libsieve_storage_data.la \
+ $(strgdir)/file/libsieve_storage_file.la \
+ $(strgdir)/dict/libsieve_storage_dict.la \
+ $(strgdir)/ldap/libsieve_storage_ldap.la $(am__DEPENDENCIES_1)
+am__DEPENDENCIES_3 = $(extdir)/vacation/libsieve_ext_vacation.la \
+ $(extdir)/subaddress/libsieve_ext_subaddress.la \
+ $(extdir)/comparator-i-ascii-numeric/libsieve_ext_comparator-i-ascii-numeric.la \
+ $(extdir)/relational/libsieve_ext_relational.la \
+ $(extdir)/regex/libsieve_ext_regex.la \
+ $(extdir)/copy/libsieve_ext_copy.la \
+ $(extdir)/imap4flags/libsieve_ext_imap4flags.la \
+ $(extdir)/include/libsieve_ext_include.la \
+ $(extdir)/body/libsieve_ext_body.la \
+ $(extdir)/variables/libsieve_ext_variables.la \
+ $(extdir)/enotify/libsieve_ext_enotify.la \
+ $(extdir)/notify/libsieve_ext_notify.la \
+ $(extdir)/environment/libsieve_ext_environment.la \
+ $(extdir)/mailbox/libsieve_ext_mailbox.la \
+ $(extdir)/date/libsieve_ext_date.la \
+ $(extdir)/spamvirustest/libsieve_ext_spamvirustest.la \
+ $(extdir)/ihave/libsieve_ext_ihave.la \
+ $(extdir)/editheader/libsieve_ext_editheader.la \
+ $(extdir)/duplicate/libsieve_ext_duplicate.la \
+ $(extdir)/index/libsieve_ext_index.la \
+ $(extdir)/metadata/libsieve_ext_metadata.la \
+ $(extdir)/mime/libsieve_ext_mime.la \
+ $(extdir)/special-use/libsieve_ext_special_use.la \
+ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+ $(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
+ $(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \
+ $(am__DEPENDENCIES_1)
+am__objects_1 = cmp-i-octet.lo cmp-i-ascii-casemap.lo
+am__objects_2 = mcht-is.lo mcht-contains.lo mcht-matches.lo
+am__objects_3 = tst-truefalse.lo tst-not.lo tst-anyof.lo tst-allof.lo \
+ tst-address.lo tst-header.lo tst-exists.lo tst-size.lo
+am__objects_4 = cmd-require.lo cmd-stop.lo cmd-if.lo cmd-keep.lo \
+ cmd-redirect.lo cmd-discard.lo
+am__objects_5 = ext-fileinto.lo ext-reject.lo ext-envelope.lo \
+ ext-encoded-character.lo
+am_libdovecot_sieve_la_OBJECTS = sieve-settings.lo sieve-message.lo \
+ sieve-smtp.lo sieve-lexer.lo sieve-script.lo sieve-storage.lo \
+ sieve-storage-sync.lo sieve-ast.lo sieve-binary.lo \
+ sieve-binary-file.lo sieve-binary-code.lo \
+ sieve-binary-debug.lo sieve-parser.lo sieve-address.lo \
+ sieve-validator.lo sieve-generator.lo sieve-execute.lo \
+ sieve-interpreter.lo sieve-runtime-trace.lo \
+ sieve-code-dumper.lo sieve-binary-dumper.lo sieve-result.lo \
+ sieve-error.lo sieve-objects.lo sieve-stringlist.lo \
+ sieve-comparators.lo sieve-match-types.lo \
+ sieve-address-parts.lo sieve-address-source.lo sieve-match.lo \
+ sieve-commands.lo sieve-code.lo sieve-actions.lo \
+ sieve-extensions.lo sieve-plugins.lo $(am__objects_1) \
+ $(am__objects_2) $(am__objects_3) $(am__objects_4) \
+ $(am__objects_5) sieve.lo
+libdovecot_sieve_la_OBJECTS = $(am_libdovecot_sieve_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-discard.Plo \
+ ./$(DEPDIR)/cmd-if.Plo ./$(DEPDIR)/cmd-keep.Plo \
+ ./$(DEPDIR)/cmd-redirect.Plo ./$(DEPDIR)/cmd-require.Plo \
+ ./$(DEPDIR)/cmd-stop.Plo ./$(DEPDIR)/cmp-i-ascii-casemap.Plo \
+ ./$(DEPDIR)/cmp-i-octet.Plo \
+ ./$(DEPDIR)/ext-encoded-character.Plo \
+ ./$(DEPDIR)/ext-envelope.Plo ./$(DEPDIR)/ext-fileinto.Plo \
+ ./$(DEPDIR)/ext-reject.Plo ./$(DEPDIR)/mcht-contains.Plo \
+ ./$(DEPDIR)/mcht-is.Plo ./$(DEPDIR)/mcht-matches.Plo \
+ ./$(DEPDIR)/sieve-actions.Plo \
+ ./$(DEPDIR)/sieve-address-parts.Plo \
+ ./$(DEPDIR)/sieve-address-source.Plo \
+ ./$(DEPDIR)/sieve-address.Plo ./$(DEPDIR)/sieve-ast.Plo \
+ ./$(DEPDIR)/sieve-binary-code.Plo \
+ ./$(DEPDIR)/sieve-binary-debug.Plo \
+ ./$(DEPDIR)/sieve-binary-dumper.Plo \
+ ./$(DEPDIR)/sieve-binary-file.Plo ./$(DEPDIR)/sieve-binary.Plo \
+ ./$(DEPDIR)/sieve-code-dumper.Plo ./$(DEPDIR)/sieve-code.Plo \
+ ./$(DEPDIR)/sieve-commands.Plo \
+ ./$(DEPDIR)/sieve-comparators.Plo ./$(DEPDIR)/sieve-error.Plo \
+ ./$(DEPDIR)/sieve-execute.Plo ./$(DEPDIR)/sieve-extensions.Plo \
+ ./$(DEPDIR)/sieve-generator.Plo \
+ ./$(DEPDIR)/sieve-interpreter.Plo ./$(DEPDIR)/sieve-lexer.Plo \
+ ./$(DEPDIR)/sieve-match-types.Plo ./$(DEPDIR)/sieve-match.Plo \
+ ./$(DEPDIR)/sieve-message.Plo ./$(DEPDIR)/sieve-objects.Plo \
+ ./$(DEPDIR)/sieve-parser.Plo ./$(DEPDIR)/sieve-plugins.Plo \
+ ./$(DEPDIR)/sieve-result.Plo \
+ ./$(DEPDIR)/sieve-runtime-trace.Plo \
+ ./$(DEPDIR)/sieve-script.Plo ./$(DEPDIR)/sieve-settings.Plo \
+ ./$(DEPDIR)/sieve-smtp.Plo ./$(DEPDIR)/sieve-storage-sync.Plo \
+ ./$(DEPDIR)/sieve-storage.Plo ./$(DEPDIR)/sieve-stringlist.Plo \
+ ./$(DEPDIR)/sieve-validator.Plo ./$(DEPDIR)/sieve.Plo \
+ ./$(DEPDIR)/tst-address.Plo ./$(DEPDIR)/tst-allof.Plo \
+ ./$(DEPDIR)/tst-anyof.Plo ./$(DEPDIR)/tst-exists.Plo \
+ ./$(DEPDIR)/tst-header.Plo ./$(DEPDIR)/tst-not.Plo \
+ ./$(DEPDIR)/tst-size.Plo ./$(DEPDIR)/tst-truefalse.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdovecot_sieve_la_SOURCES)
+DIST_SOURCES = $(libdovecot_sieve_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(pkginc_lib_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = util storage plugins
+dovecot_pkglib_LTLIBRARIES = libdovecot-sieve.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -I$(top_srcdir)/src/lib-sieve/util \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+tests = \
+ tst-truefalse.c \
+ tst-not.c \
+ tst-anyof.c \
+ tst-allof.c \
+ tst-address.c \
+ tst-header.c \
+ tst-exists.c \
+ tst-size.c
+
+commands = \
+ cmd-require.c \
+ cmd-stop.c \
+ cmd-if.c \
+ cmd-keep.c \
+ cmd-redirect.c \
+ cmd-discard.c
+
+extensions = \
+ ext-fileinto.c \
+ ext-reject.c \
+ ext-envelope.c \
+ ext-encoded-character.c
+
+match_types = \
+ mcht-is.c \
+ mcht-contains.c \
+ mcht-matches.c
+
+comparators = \
+ cmp-i-octet.c \
+ cmp-i-ascii-casemap.c
+
+@BUILD_UNFINISHED_TRUE@unfinished_storages =
+@BUILD_UNFINISHED_TRUE@unfinished_plugins =
+strgdir = $(top_builddir)/src/lib-sieve/storage
+storages = \
+ $(strgdir)/data/libsieve_storage_data.la \
+ $(strgdir)/file/libsieve_storage_file.la \
+ $(strgdir)/dict/libsieve_storage_dict.la \
+ $(strgdir)/ldap/libsieve_storage_ldap.la \
+ $(unfinished_storages)
+
+extdir = $(top_builddir)/src/lib-sieve/plugins
+plugins = \
+ $(extdir)/vacation/libsieve_ext_vacation.la \
+ $(extdir)/subaddress/libsieve_ext_subaddress.la \
+ $(extdir)/comparator-i-ascii-numeric/libsieve_ext_comparator-i-ascii-numeric.la \
+ $(extdir)/relational/libsieve_ext_relational.la \
+ $(extdir)/regex/libsieve_ext_regex.la \
+ $(extdir)/copy/libsieve_ext_copy.la \
+ $(extdir)/imap4flags/libsieve_ext_imap4flags.la \
+ $(extdir)/include/libsieve_ext_include.la \
+ $(extdir)/body/libsieve_ext_body.la \
+ $(extdir)/variables/libsieve_ext_variables.la \
+ $(extdir)/enotify/libsieve_ext_enotify.la \
+ $(extdir)/notify/libsieve_ext_notify.la \
+ $(extdir)/environment/libsieve_ext_environment.la \
+ $(extdir)/mailbox/libsieve_ext_mailbox.la \
+ $(extdir)/date/libsieve_ext_date.la \
+ $(extdir)/spamvirustest/libsieve_ext_spamvirustest.la \
+ $(extdir)/ihave/libsieve_ext_ihave.la \
+ $(extdir)/editheader/libsieve_ext_editheader.la \
+ $(extdir)/duplicate/libsieve_ext_duplicate.la \
+ $(extdir)/index/libsieve_ext_index.la \
+ $(extdir)/metadata/libsieve_ext_metadata.la \
+ $(extdir)/mime/libsieve_ext_mime.la \
+ $(extdir)/special-use/libsieve_ext_special_use.la \
+ $(extdir)/vnd.dovecot/debug/libsieve_ext_debug.la \
+ $(extdir)/vnd.dovecot/environment/libsieve_ext_vnd_environment.la \
+ $(extdir)/vnd.dovecot/report/libsieve_ext_vnd_report.la \
+ $(unfinished_plugins)
+
+libdovecot_sieve_la_DEPENDENCIES = \
+ $(storages) \
+ $(plugins) \
+ $(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+libdovecot_sieve_la_LIBADD = \
+ $(storages) \
+ $(plugins) \
+ $(top_builddir)/src/lib-sieve/util/libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+libdovecot_sieve_la_SOURCES = \
+ sieve-settings.c \
+ sieve-message.c \
+ sieve-smtp.c \
+ sieve-lexer.c \
+ sieve-script.c \
+ sieve-storage.c \
+ sieve-storage-sync.c \
+ sieve-ast.c \
+ sieve-binary.c \
+ sieve-binary-file.c \
+ sieve-binary-code.c \
+ sieve-binary-debug.c \
+ sieve-parser.c \
+ sieve-address.c \
+ sieve-validator.c \
+ sieve-generator.c \
+ sieve-execute.c \
+ sieve-interpreter.c \
+ sieve-runtime-trace.c \
+ sieve-code-dumper.c \
+ sieve-binary-dumper.c \
+ sieve-result.c \
+ sieve-error.c \
+ sieve-objects.c \
+ sieve-stringlist.c \
+ sieve-comparators.c \
+ sieve-match-types.c \
+ sieve-address-parts.c \
+ sieve-address-source.c \
+ sieve-match.c \
+ sieve-commands.c \
+ sieve-code.c \
+ sieve-actions.c \
+ sieve-extensions.c \
+ sieve-plugins.c \
+ $(comparators) \
+ $(match_types) \
+ $(tests) \
+ $(commands) \
+ $(extensions) \
+ sieve.c
+
+headers = \
+ sieve-config.h \
+ sieve-types.h \
+ sieve-common.h \
+ sieve-limits.h \
+ sieve-settings.h \
+ sieve-message.h \
+ sieve-smtp.h \
+ sieve-lexer.h \
+ sieve-script.h \
+ sieve-script-private.h \
+ sieve-storage.h \
+ sieve-storage-private.h \
+ sieve-ast.h \
+ sieve-binary.h \
+ sieve-binary-private.h \
+ sieve-parser.h \
+ sieve-address.h \
+ sieve-validator.h \
+ sieve-generator.h \
+ sieve-execute.h \
+ sieve-interpreter.h \
+ sieve-runtime-trace.h \
+ sieve-runtime.h \
+ sieve-code-dumper.h \
+ sieve-binary-dumper.h \
+ sieve-dump.h \
+ sieve-result.h \
+ sieve-error.h \
+ sieve-error-private.h \
+ sieve-objects.h \
+ sieve-stringlist.h \
+ sieve-match.h \
+ sieve-comparators.h \
+ sieve-match-types.h \
+ sieve-address-parts.h \
+ sieve-address-source.h \
+ sieve-commands.h \
+ sieve-code.h \
+ sieve-actions.h \
+ sieve-extensions.h \
+ sieve-plugins.h \
+ sieve.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-dovecot_pkglibLTLIBRARIES: $(dovecot_pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(dovecot_pkglib_LTLIBRARIES)'; test -n "$(dovecot_pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(dovecot_pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(dovecot_pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(dovecot_pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(dovecot_pkglibdir)"; \
+ }
+
+uninstall-dovecot_pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dovecot_pkglib_LTLIBRARIES)'; test -n "$(dovecot_pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(dovecot_pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(dovecot_pkglibdir)/$$f"; \
+ done
+
+clean-dovecot_pkglibLTLIBRARIES:
+ -test -z "$(dovecot_pkglib_LTLIBRARIES)" || rm -f $(dovecot_pkglib_LTLIBRARIES)
+ @list='$(dovecot_pkglib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libdovecot-sieve.la: $(libdovecot_sieve_la_OBJECTS) $(libdovecot_sieve_la_DEPENDENCIES) $(EXTRA_libdovecot_sieve_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) -rpath $(dovecot_pkglibdir) $(libdovecot_sieve_la_OBJECTS) $(libdovecot_sieve_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-discard.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-if.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-keep.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-redirect.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-require.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-stop.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmp-i-ascii-casemap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmp-i-octet.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-encoded-character.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-envelope.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-fileinto.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-reject.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-contains.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-is.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-matches.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-actions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-address-parts.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-address-source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-address.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-ast.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-binary-code.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-binary-debug.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-binary-dumper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-binary-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-binary.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-code-dumper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-code.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-commands.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-comparators.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-error.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-execute.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-extensions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-generator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-interpreter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-match-types.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-match.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-objects.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-plugins.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-result.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-runtime-trace.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-smtp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-storage-sync.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-stringlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-validator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-address.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-allof.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-anyof.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-exists.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-header.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-not.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-size.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-truefalse.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(dovecot_pkglibdir)" "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-dovecot_pkglibLTLIBRARIES clean-generic clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/cmd-discard.Plo
+ -rm -f ./$(DEPDIR)/cmd-if.Plo
+ -rm -f ./$(DEPDIR)/cmd-keep.Plo
+ -rm -f ./$(DEPDIR)/cmd-redirect.Plo
+ -rm -f ./$(DEPDIR)/cmd-require.Plo
+ -rm -f ./$(DEPDIR)/cmd-stop.Plo
+ -rm -f ./$(DEPDIR)/cmp-i-ascii-casemap.Plo
+ -rm -f ./$(DEPDIR)/cmp-i-octet.Plo
+ -rm -f ./$(DEPDIR)/ext-encoded-character.Plo
+ -rm -f ./$(DEPDIR)/ext-envelope.Plo
+ -rm -f ./$(DEPDIR)/ext-fileinto.Plo
+ -rm -f ./$(DEPDIR)/ext-reject.Plo
+ -rm -f ./$(DEPDIR)/mcht-contains.Plo
+ -rm -f ./$(DEPDIR)/mcht-is.Plo
+ -rm -f ./$(DEPDIR)/mcht-matches.Plo
+ -rm -f ./$(DEPDIR)/sieve-actions.Plo
+ -rm -f ./$(DEPDIR)/sieve-address-parts.Plo
+ -rm -f ./$(DEPDIR)/sieve-address-source.Plo
+ -rm -f ./$(DEPDIR)/sieve-address.Plo
+ -rm -f ./$(DEPDIR)/sieve-ast.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-code.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-debug.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-dumper.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-file.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary.Plo
+ -rm -f ./$(DEPDIR)/sieve-code-dumper.Plo
+ -rm -f ./$(DEPDIR)/sieve-code.Plo
+ -rm -f ./$(DEPDIR)/sieve-commands.Plo
+ -rm -f ./$(DEPDIR)/sieve-comparators.Plo
+ -rm -f ./$(DEPDIR)/sieve-error.Plo
+ -rm -f ./$(DEPDIR)/sieve-execute.Plo
+ -rm -f ./$(DEPDIR)/sieve-extensions.Plo
+ -rm -f ./$(DEPDIR)/sieve-generator.Plo
+ -rm -f ./$(DEPDIR)/sieve-interpreter.Plo
+ -rm -f ./$(DEPDIR)/sieve-lexer.Plo
+ -rm -f ./$(DEPDIR)/sieve-match-types.Plo
+ -rm -f ./$(DEPDIR)/sieve-match.Plo
+ -rm -f ./$(DEPDIR)/sieve-message.Plo
+ -rm -f ./$(DEPDIR)/sieve-objects.Plo
+ -rm -f ./$(DEPDIR)/sieve-parser.Plo
+ -rm -f ./$(DEPDIR)/sieve-plugins.Plo
+ -rm -f ./$(DEPDIR)/sieve-result.Plo
+ -rm -f ./$(DEPDIR)/sieve-runtime-trace.Plo
+ -rm -f ./$(DEPDIR)/sieve-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-settings.Plo
+ -rm -f ./$(DEPDIR)/sieve-smtp.Plo
+ -rm -f ./$(DEPDIR)/sieve-storage-sync.Plo
+ -rm -f ./$(DEPDIR)/sieve-storage.Plo
+ -rm -f ./$(DEPDIR)/sieve-stringlist.Plo
+ -rm -f ./$(DEPDIR)/sieve-validator.Plo
+ -rm -f ./$(DEPDIR)/sieve.Plo
+ -rm -f ./$(DEPDIR)/tst-address.Plo
+ -rm -f ./$(DEPDIR)/tst-allof.Plo
+ -rm -f ./$(DEPDIR)/tst-anyof.Plo
+ -rm -f ./$(DEPDIR)/tst-exists.Plo
+ -rm -f ./$(DEPDIR)/tst-header.Plo
+ -rm -f ./$(DEPDIR)/tst-not.Plo
+ -rm -f ./$(DEPDIR)/tst-size.Plo
+ -rm -f ./$(DEPDIR)/tst-truefalse.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-dovecot_pkglibLTLIBRARIES \
+ install-pkginc_libHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/cmd-discard.Plo
+ -rm -f ./$(DEPDIR)/cmd-if.Plo
+ -rm -f ./$(DEPDIR)/cmd-keep.Plo
+ -rm -f ./$(DEPDIR)/cmd-redirect.Plo
+ -rm -f ./$(DEPDIR)/cmd-require.Plo
+ -rm -f ./$(DEPDIR)/cmd-stop.Plo
+ -rm -f ./$(DEPDIR)/cmp-i-ascii-casemap.Plo
+ -rm -f ./$(DEPDIR)/cmp-i-octet.Plo
+ -rm -f ./$(DEPDIR)/ext-encoded-character.Plo
+ -rm -f ./$(DEPDIR)/ext-envelope.Plo
+ -rm -f ./$(DEPDIR)/ext-fileinto.Plo
+ -rm -f ./$(DEPDIR)/ext-reject.Plo
+ -rm -f ./$(DEPDIR)/mcht-contains.Plo
+ -rm -f ./$(DEPDIR)/mcht-is.Plo
+ -rm -f ./$(DEPDIR)/mcht-matches.Plo
+ -rm -f ./$(DEPDIR)/sieve-actions.Plo
+ -rm -f ./$(DEPDIR)/sieve-address-parts.Plo
+ -rm -f ./$(DEPDIR)/sieve-address-source.Plo
+ -rm -f ./$(DEPDIR)/sieve-address.Plo
+ -rm -f ./$(DEPDIR)/sieve-ast.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-code.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-debug.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-dumper.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary-file.Plo
+ -rm -f ./$(DEPDIR)/sieve-binary.Plo
+ -rm -f ./$(DEPDIR)/sieve-code-dumper.Plo
+ -rm -f ./$(DEPDIR)/sieve-code.Plo
+ -rm -f ./$(DEPDIR)/sieve-commands.Plo
+ -rm -f ./$(DEPDIR)/sieve-comparators.Plo
+ -rm -f ./$(DEPDIR)/sieve-error.Plo
+ -rm -f ./$(DEPDIR)/sieve-execute.Plo
+ -rm -f ./$(DEPDIR)/sieve-extensions.Plo
+ -rm -f ./$(DEPDIR)/sieve-generator.Plo
+ -rm -f ./$(DEPDIR)/sieve-interpreter.Plo
+ -rm -f ./$(DEPDIR)/sieve-lexer.Plo
+ -rm -f ./$(DEPDIR)/sieve-match-types.Plo
+ -rm -f ./$(DEPDIR)/sieve-match.Plo
+ -rm -f ./$(DEPDIR)/sieve-message.Plo
+ -rm -f ./$(DEPDIR)/sieve-objects.Plo
+ -rm -f ./$(DEPDIR)/sieve-parser.Plo
+ -rm -f ./$(DEPDIR)/sieve-plugins.Plo
+ -rm -f ./$(DEPDIR)/sieve-result.Plo
+ -rm -f ./$(DEPDIR)/sieve-runtime-trace.Plo
+ -rm -f ./$(DEPDIR)/sieve-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-settings.Plo
+ -rm -f ./$(DEPDIR)/sieve-smtp.Plo
+ -rm -f ./$(DEPDIR)/sieve-storage-sync.Plo
+ -rm -f ./$(DEPDIR)/sieve-storage.Plo
+ -rm -f ./$(DEPDIR)/sieve-stringlist.Plo
+ -rm -f ./$(DEPDIR)/sieve-validator.Plo
+ -rm -f ./$(DEPDIR)/sieve.Plo
+ -rm -f ./$(DEPDIR)/tst-address.Plo
+ -rm -f ./$(DEPDIR)/tst-allof.Plo
+ -rm -f ./$(DEPDIR)/tst-anyof.Plo
+ -rm -f ./$(DEPDIR)/tst-exists.Plo
+ -rm -f ./$(DEPDIR)/tst-header.Plo
+ -rm -f ./$(DEPDIR)/tst-not.Plo
+ -rm -f ./$(DEPDIR)/tst-size.Plo
+ -rm -f ./$(DEPDIR)/tst-truefalse.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-dovecot_pkglibLTLIBRARIES \
+ uninstall-pkginc_libHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean \
+ clean-dovecot_pkglibLTLIBRARIES clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dovecot_pkglibLTLIBRARIES \
+ install-dvi install-dvi-am install-exec install-exec-am \
+ install-html install-html-am install-info install-info-am \
+ install-man install-pdf install-pdf-am \
+ install-pkginc_libHEADERS install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-dovecot_pkglibLTLIBRARIES \
+ uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/cmd-discard.c b/pigeonhole/src/lib-sieve/cmd-discard.c
new file mode 100644
index 0000000..de460ae
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-discard.c
@@ -0,0 +1,173 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-dump.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+/*
+ * Discard command
+ *
+ * Syntax
+ * discard
+ */
+
+static bool
+cmd_discard_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx ATTR_UNUSED);
+
+const struct sieve_command_def cmd_discard = {
+ .identifier = "discard",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_discard_generate
+};
+
+/*
+ * Discard operation
+ */
+
+static bool
+cmd_discard_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_discard_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def cmd_discard_operation = {
+ .mnemonic = "DISCARD",
+ .code = SIEVE_OPERATION_DISCARD,
+ .dump = cmd_discard_operation_dump,
+ .execute = cmd_discard_operation_execute
+};
+
+/*
+ * Discard actions
+ */
+
+static bool
+act_discard_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2);
+static int
+act_discard_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_discard_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_discard_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep);
+
+const struct sieve_action_def act_discard = {
+ .name = "discard",
+ .equals = act_discard_equals,
+ .check_duplicate = act_discard_check_duplicate,
+ .print = act_discard_print,
+ .execute = act_discard_execute,
+};
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_discard_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &cmd_discard_operation);
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_discard_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "DISCARD");
+ sieve_code_descend(denv);
+
+ return (sieve_action_opr_optional_dump(denv, address, NULL) == 0);
+}
+
+/*
+ * Interpretation
+ */
+
+static int
+cmd_discard_operation_execute(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+ "discard action; cancel implicit keep");
+
+ if (sieve_result_add_action(renv, NULL, "discard", &act_discard,
+ NULL, NULL, 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action implementation
+ */
+
+static bool
+act_discard_equals(const struct sieve_script_env *senv ATTR_UNUSED,
+ const struct sieve_action *act1 ATTR_UNUSED,
+ const struct sieve_action *act2 ATTR_UNUSED)
+{
+ return TRUE;
+}
+
+static int
+act_discard_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *act ATTR_UNUSED,
+ const struct sieve_action *act_other ATTR_UNUSED)
+{
+ return 1;
+}
+
+static void
+act_discard_print(const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ sieve_result_action_printf(rpenv, "discard");
+
+ *keep = FALSE;
+}
+
+static int
+act_discard_execute(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED, bool *keep)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+
+ eenv->exec_status->significant_action_executed = TRUE;
+
+ struct event_passthrough *e = sieve_action_create_finish_event(aenv);
+
+ sieve_result_event_log(aenv, e->event(),
+ "Marked message to be discarded if not explicitly delivered "
+ "(discard action)");
+ *keep = FALSE;
+
+ return SIEVE_EXEC_OK;
+}
+
diff --git a/pigeonhole/src/lib-sieve/cmd-if.c b/pigeonhole/src/lib-sieve/cmd-if.c
new file mode 100644
index 0000000..2ae186e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-if.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_if_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_elsif_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_if_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ int *const_current, int const_next);
+static bool cmd_if_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+static bool cmd_else_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+/* If command
+ *
+ * Syntax:
+ * if <test1: test> <block1: block>
+ */
+
+const struct sieve_command_def cmd_if = {
+ .identifier = "if",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 1,
+ .block_allowed = TRUE,
+ .block_required = TRUE,
+ .validate = cmd_if_validate,
+ .validate_const = cmd_if_validate_const,
+ .generate = cmd_if_generate
+};
+
+/* ElsIf command
+ *
+ * Santax:
+ * elsif <test2: test> <block2: block>
+ */
+
+const struct sieve_command_def cmd_elsif = {
+ .identifier = "elsif",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 1,
+ .block_allowed = TRUE,
+ .block_required = TRUE,
+ .validate = cmd_elsif_validate,
+ .validate_const = cmd_if_validate_const,
+ .generate = cmd_if_generate
+};
+
+/* Else command
+ *
+ * Syntax:
+ * else <block>
+ */
+
+const struct sieve_command_def cmd_else = {
+ .identifier = "else",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = TRUE,
+ .block_required = TRUE,
+ .validate = cmd_elsif_validate,
+ .validate_const = cmd_if_validate_const,
+ .generate = cmd_else_generate
+};
+
+/*
+ * Context management
+ */
+
+struct cmd_if_context_data {
+ struct cmd_if_context_data *previous;
+ struct cmd_if_context_data *next;
+
+ int const_condition;
+
+ bool jump_generated;
+ sieve_size_t exit_jump;
+};
+
+static void cmd_if_initialize_context_data
+(struct sieve_command *cmd, struct cmd_if_context_data *previous)
+{
+ struct cmd_if_context_data *cmd_data;
+
+ /* Assign context */
+ cmd_data = p_new(sieve_command_pool(cmd), struct cmd_if_context_data, 1);
+ cmd_data->exit_jump = 0;
+ cmd_data->jump_generated = FALSE;
+
+ /* Update linked list of contexts */
+ cmd_data->previous = previous;
+ cmd_data->next = NULL;
+ if ( previous != NULL )
+ previous->next = cmd_data;
+
+ /* Check const status */
+ cmd_data->const_condition = -1;
+ while ( previous != NULL ) {
+ if ( previous->const_condition > 0 ) {
+ cmd_data->const_condition = 0;
+ break;
+ }
+ previous = previous->previous;
+ }
+
+ /* Assign to command context */
+ cmd->data = cmd_data;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_if_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ /* Start if-command structure */
+ cmd_if_initialize_context_data(cmd, NULL);
+
+ return TRUE;
+}
+
+static bool cmd_elsif_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_command *prev;
+
+ i_assert(cmd != NULL);
+ prev = sieve_command_prev(cmd);
+
+ /* Check valid command placement */
+ if ( prev == NULL ||
+ ( !sieve_command_is(prev, cmd_if) && !sieve_command_is(prev, cmd_elsif) ) )
+ {
+ sieve_command_validate_error(valdtr, cmd,
+ "the %s command must follow an if or elseif command",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+
+ /* Previous command in this block is 'if' or 'elsif', so we can safely refer
+ * to its context data
+ */
+ cmd_if_initialize_context_data(cmd, prev->data);
+
+ return TRUE;
+}
+
+static bool cmd_if_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd,
+ int *const_current, int const_next)
+{
+ struct cmd_if_context_data *cmd_data =
+ (struct cmd_if_context_data *) cmd->data;
+
+ if ( cmd_data != NULL ) {
+ if ( cmd_data->const_condition == 0 ) {
+ *const_current = cmd_data->const_condition;
+ return FALSE;
+ }
+
+ cmd_data->const_condition = const_next;
+ }
+
+ *const_current = const_next;
+
+ return ( const_next < 0 );
+}
+
+/*
+ * Code generation
+ */
+
+/* The if command does not generate specific IF-ELSIF-ELSE opcodes, but only uses
+ * JMP instructions. This is why the implementation of the if command does not
+ * include an opcode implementation.
+ */
+
+static void cmd_if_resolve_exit_jumps
+(struct sieve_binary_block *sblock, struct cmd_if_context_data *cmd_data)
+{
+ struct cmd_if_context_data *if_ctx = cmd_data->previous;
+
+ /* Iterate backwards through all if-command contexts and resolve the
+ * exit jumps to the current code position.
+ */
+ while ( if_ctx != NULL ) {
+ if ( if_ctx->jump_generated )
+ sieve_binary_resolve_offset(sblock, if_ctx->exit_jump);
+ if_ctx = if_ctx->previous;
+ }
+}
+
+static bool cmd_if_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ struct cmd_if_context_data *cmd_data =
+ (struct cmd_if_context_data *) cmd->data;
+ struct sieve_ast_node *test;
+ struct sieve_jumplist jmplist;
+
+ /* Generate test condition */
+ if ( cmd_data->const_condition < 0 ) {
+ /* Prepare jumplist */
+ sieve_jumplist_init_temp(&jmplist, sblock);
+
+ test = sieve_ast_test_first(cmd->ast_node);
+ if ( !sieve_generate_test(cgenv, test, &jmplist, FALSE) )
+ return FALSE;
+ }
+
+ /* Case true { */
+ if ( cmd_data->const_condition != 0 ) {
+ if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+ return FALSE;
+ }
+
+ /* Are we the final command in this if-elsif-else structure? */
+ if ( cmd_data->next == NULL || cmd_data->const_condition == 1 ) {
+ /* Yes, Resolve previous exit jumps to this point */
+ cmd_if_resolve_exit_jumps(sblock, cmd_data);
+
+ } else if ( cmd_data->const_condition < 0 ) {
+ /* No, generate jump to end of if-elsif-else structure (resolved later)
+ * This of course is not necessary if the {} block contains a command
+ * like stop at top level that unconditionally exits the block already
+ * anyway.
+ */
+ if ( !sieve_command_block_exits_unconditionally(cmd) ) {
+ sieve_operation_emit(sblock, NULL, &sieve_jmp_operation);
+ cmd_data->exit_jump = sieve_binary_emit_offset(sblock, 0);
+ cmd_data->jump_generated = TRUE;
+ }
+ }
+
+ if ( cmd_data->const_condition < 0 ) {
+ /* Case false ... (subsequent elsif/else commands might generate more) */
+ sieve_jumplist_resolve(&jmplist);
+ }
+
+ return TRUE;
+}
+
+static bool cmd_else_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct cmd_if_context_data *cmd_data =
+ (struct cmd_if_context_data *) cmd->data;
+
+ /* Else { */
+ if ( cmd_data->const_condition != 0 ) {
+ if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+ return FALSE;
+
+ /* } End: resolve all exit blocks */
+ cmd_if_resolve_exit_jumps(cgenv->sblock, cmd_data);
+ }
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/cmd-keep.c b/pigeonhole/src/lib-sieve/cmd-keep.c
new file mode 100644
index 0000000..b619a80
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-keep.c
@@ -0,0 +1,113 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-dump.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+/*
+ * Keep command
+ *
+ * Syntax:
+ * keep
+ */
+
+static bool cmd_keep_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_keep = {
+ .identifier = "keep",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_keep_generate
+};
+
+/*
+ * Keep operation
+ */
+
+static bool cmd_keep_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_keep_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_keep_operation = {
+ .mnemonic = "KEEP",
+ .code = SIEVE_OPERATION_KEEP,
+ .dump = cmd_keep_operation_dump,
+ .execute = cmd_keep_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_keep_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ /* Emit opcode */
+ sieve_operation_emit(cgenv->sblock, NULL, &cmd_keep_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_keep_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "KEEP");
+ sieve_code_descend(denv);
+
+ return ( sieve_action_opr_optional_dump(denv, address, NULL) == 0 );
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_keep_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_side_effects_list *slist = NULL;
+ int ret = 0;
+
+ /*
+ * Read data
+ */
+
+ /* Optional operands (side effects only) */
+ if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS,
+ "keep action; store message in default mailbox");
+
+ /* Add keep action to result.
+ */
+ if ( sieve_result_add_keep(renv, slist) < 0 )
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/cmd-redirect.c b/pigeonhole/src/lib-sieve/cmd-redirect.c
new file mode 100644
index 0000000..6a3b0a4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-redirect.c
@@ -0,0 +1,677 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "strfuncs.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-address.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+#include "sieve-result.h"
+#include "sieve-smtp.h"
+#include "sieve-message.h"
+
+#include <stdio.h>
+
+/*
+ * Redirect command
+ *
+ * Syntax
+ * redirect <address: string>
+ */
+
+static bool
+cmd_redirect_validate(struct sieve_validator *validator,
+ struct sieve_command *cmd);
+static bool
+cmd_redirect_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_redirect = {
+ .identifier = "redirect",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_redirect_validate,
+ .generate = cmd_redirect_generate
+};
+
+/*
+ * Redirect operation
+ */
+
+static bool
+cmd_redirect_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_redirect_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def cmd_redirect_operation = {
+ .mnemonic = "REDIRECT",
+ .code = SIEVE_OPERATION_REDIRECT,
+ .dump = cmd_redirect_operation_dump,
+ .execute = cmd_redirect_operation_execute
+};
+
+/*
+ * Redirect action
+ */
+
+static bool
+act_redirect_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2);
+static int
+act_redirect_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_redirect_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context);
+static int
+act_redirect_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep);
+static int
+act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+
+const struct sieve_action_def act_redirect = {
+ .name = "redirect",
+ .flags = SIEVE_ACTFLAG_TRIES_DELIVER,
+ .equals = act_redirect_equals,
+ .check_duplicate = act_redirect_check_duplicate,
+ .print = act_redirect_print,
+ .start = act_redirect_start,
+ .execute = act_redirect_execute,
+ .commit = act_redirect_commit,
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_redirect_validate(struct sieve_validator *validator,
+ struct sieve_command *cmd)
+{
+ struct sieve_instance *svinst = sieve_validator_svinst(validator);
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check and activate address argument */
+
+ if (!sieve_validate_positional_argument(validator, cmd, arg, "address",
+ 1, SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(validator, cmd, arg, FALSE))
+ return FALSE;
+
+ /* We can only assess the validity of the outgoing address when it is
+ * a string literal. For runtime-generated strings this needs to be
+ * done at runtime.
+ */
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *raw_address = sieve_ast_argument_str(arg);
+ const char *error;
+ bool result;
+
+ T_BEGIN {
+ /* Parse the address */
+ result = sieve_address_validate_str(raw_address, &error);
+ if (!result) {
+ sieve_argument_validate_error(
+ validator, arg,
+ "specified redirect address '%s' is invalid: %s",
+ str_sanitize(str_c(raw_address),128),
+ error);
+ }
+ } T_END;
+
+ return result;
+ }
+
+ if (svinst->max_redirects == 0) {
+ sieve_command_validate_error(validator, cmd,
+ "local policy prohibits the use of a redirect action");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_redirect_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &cmd_redirect_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_redirect_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "REDIRECT");
+ sieve_code_descend(denv);
+
+ if (sieve_action_opr_optional_dump(denv, address, NULL) != 0)
+ return FALSE;
+
+ return sieve_opr_string_dump(denv, address, "address");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+cmd_redirect_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_side_effects_list *slist = NULL;
+ string_t *redirect;
+ const struct smtp_address *to_address;
+ const char *error;
+ int ret;
+
+ /*
+ * Read data
+ */
+
+ /* Optional operands (side effects only) */
+ if (sieve_action_opr_optional_read(renv, address, NULL,
+ &ret, &slist) != 0)
+ return ret;
+
+ /* Read the address */
+ if ((ret = sieve_opr_string_read(renv, address, "address",
+ &redirect)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ /* Parse the address */
+ to_address = sieve_address_parse_str(redirect, &error);
+ if (to_address == NULL) {
+ sieve_runtime_error(renv, NULL,
+ "specified redirect address '%s' is invalid: %s",
+ str_sanitize(str_c(redirect),128), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if (svinst->max_redirects == 0) {
+ sieve_runtime_error(renv, NULL,
+ "local policy prohibits the use of a redirect action");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
+ sieve_runtime_trace(renv, 0, "redirect action");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "forward message to address %s",
+ smtp_address_encode_path(to_address));
+ }
+
+ /* Add redirect action to the result */
+
+ return sieve_act_redirect_add_to_result(renv, "redirect", slist,
+ to_address);
+}
+
+/*
+ * Action implementation
+ */
+
+struct act_redirect_transaction {
+ const char *msg_id, *new_msg_id;
+ const char *dupeid;
+
+ bool skip_redirect:1;
+};
+
+static bool
+act_redirect_equals(const struct sieve_script_env *senv ATTR_UNUSED,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2)
+{
+ struct act_redirect_context *rd_ctx1 =
+ (struct act_redirect_context *)act1->context;
+ struct act_redirect_context *rd_ctx2 =
+ (struct act_redirect_context *)act2->context;
+
+ /* Address is already normalized */
+ return (smtp_address_equals(rd_ctx1->to_address, rd_ctx2->to_address));
+}
+
+static int
+act_redirect_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return (act_redirect_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
+}
+
+static void
+act_redirect_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ struct act_redirect_context *ctx =
+ (struct act_redirect_context *)action->context;
+
+ sieve_result_action_printf(rpenv, "redirect message to: %s",
+ smtp_address_encode_path(ctx->to_address));
+ *keep = FALSE;
+}
+
+static int
+act_redirect_send(const struct sieve_action_exec_env *aenv, struct mail *mail,
+ struct act_redirect_context *ctx, const char *new_msg_id)
+ ATTR_NULL(4)
+{
+ static const char *hide_headers[] = { "Return-Path" };
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct sieve_address_source env_from = svinst->redirect_from;
+ struct istream *input;
+ struct ostream *output;
+ const struct smtp_address *sender;
+ const char *error;
+ struct sieve_smtp_context *sctx;
+ int ret;
+
+ /* Just to be sure */
+ if (!sieve_smtp_available(senv)) {
+ sieve_result_global_warning(aenv, "no means to send mail");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
+ return sieve_result_mail_error(aenv, mail,
+ "failed to read input message");
+ }
+
+ /* Determine which sender to use
+
+ From RFC 5228, Section 4.2:
+
+ The envelope sender address on the outgoing message is chosen by the
+ sieve implementation. It MAY be copied from the message being
+ processed. However, if the message being processed has an empty
+ envelope sender address the outgoing message MUST also have an empty
+ envelope sender address. This last requirement is imposed to prevent
+ loops in the case where a message is redirected to an invalid address
+ when then returns a delivery status notification that also ends up
+ being redirected to the same invalid address.
+ */
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
+ /* Envelope available */
+ sender = sieve_message_get_sender(msgctx);
+ if (sender != NULL &&
+ sieve_address_source_get_address(&env_from, svinst, senv,
+ msgctx, eenv->flags,
+ &sender) < 0)
+ sender = NULL;
+ } else {
+ /* No envelope available */
+ ret = sieve_address_source_get_address(&env_from, svinst, senv,
+ msgctx, eenv->flags,
+ &sender);
+ if (ret < 0)
+ sender = NULL;
+ else if (ret == 0)
+ sender = svinst->user_email;
+ }
+
+ /* Open SMTP transport */
+ sctx = sieve_smtp_start_single(senv, ctx->to_address, sender, &output);
+
+ /* Remove unwanted headers */
+ input = i_stream_create_header_filter(
+ input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+
+ T_BEGIN {
+ string_t *hdr = t_str_new(256);
+ const struct smtp_address *user_email;
+
+ /* Prepend sieve headers (should not affect signatures) */
+ rfc2822_header_append(hdr, "X-Sieve", SIEVE_IMPLEMENTATION,
+ FALSE, NULL);
+ if (svinst->user_email == NULL &&
+ (eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
+ user_email = sieve_message_get_final_recipient(msgctx);
+ else
+ user_email = sieve_get_user_email(svinst);
+ if (user_email != NULL) {
+ rfc2822_header_append(hdr, "X-Sieve-Redirected-From",
+ smtp_address_encode(user_email),
+ FALSE, NULL);
+ }
+
+ /* Add new Message-ID if message doesn't have one */
+ if (new_msg_id != NULL)
+ rfc2822_header_write(hdr, "Message-ID", new_msg_id);
+
+ o_stream_nsend(output, str_data(hdr), str_len(hdr));
+ } T_END;
+
+ o_stream_nsend_istream(output, input);
+
+ if (input->stream_errno != 0) {
+ sieve_result_critical(aenv, "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ i_stream_unref(&input);
+ sieve_smtp_abort(sctx);
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+ i_stream_unref(&input);
+
+ /* Close SMTP transport */
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(
+ aenv, "failed to redirect message to <%s>: %s "
+ "(temporary failure)",
+ smtp_address_encode(ctx->to_address),
+ str_sanitize(error, 512));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+
+ sieve_result_global_log_error(
+ aenv, "failed to redirect message to <%s>: %s "
+ "(permanent failure)",
+ smtp_address_encode(ctx->to_address),
+ str_sanitize(error, 512));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_get_duplicate_id(struct act_redirect_context *ctx,
+ const struct sieve_action_exec_env *aenv,
+ const char *msg_id, const char **dupeid_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ struct mail *mail = msgdata->mail;
+ const struct smtp_address *recipient;
+ const char *resent_id = NULL, *list_id = NULL;
+
+ /* Read identifying headers */
+ if (mail_get_first_header(mail, "resent-message-id", &resent_id) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `resent-message-id'");
+ }
+ if (resent_id == NULL &&
+ mail_get_first_header(mail, "resent-from", &resent_id) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `resent-from'");
+ }
+ if (mail_get_first_header(mail, "list-id", &list_id) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `list-id'");
+ }
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
+ recipient = sieve_message_get_orig_recipient(msgctx);
+ else
+ recipient = sieve_get_user_email(eenv->svinst);
+
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ /* Base the duplicate ID on:
+ - the message id
+ - the recipient running this Sieve script
+ - redirect target address
+ - if this message is resent: the message-id or from-address of
+ the original message
+ - if the message came through a mailing list: the mailinglist ID
+ */
+ *dupeid_r = p_strdup_printf(
+ pool, "%s-%s-%s-%s-%s", msg_id,
+ (recipient != NULL ? smtp_address_encode(recipient) : ""),
+ smtp_address_encode(ctx->to_address),
+ (resent_id != NULL ? resent_id : ""),
+ (list_id != NULL ? list_id : ""));
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_check_loop_header(const struct sieve_action_exec_env *aenv,
+ struct mail *mail, bool *loop_detected_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ const char *const *headers;
+ const char *recipient, *user_email;
+ const struct smtp_address *addr;
+ int ret;
+
+ *loop_detected_r = FALSE;
+
+ ret = mail_get_headers(mail, "x-sieve-redirected-from", &headers);
+ if (ret < 0) {
+ return sieve_result_mail_error(
+ aenv, mail, "failed to read header field "
+ "`x-sieve-redirected-from'");
+ }
+
+ if (ret == 0)
+ return SIEVE_EXEC_OK;
+
+ recipient = user_email = NULL;
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
+ addr = sieve_message_get_final_recipient(msgctx);
+ if (addr != NULL)
+ recipient = smtp_address_encode(addr);
+ }
+ addr = sieve_get_user_email(eenv->svinst);
+ if (addr != NULL)
+ user_email = smtp_address_encode(addr);
+
+ while (*headers != NULL) {
+ const char *header = t_str_trim(*headers, " \t\r\n");
+ if (recipient != NULL && strcmp(header, recipient) == 0) {
+ *loop_detected_r = TRUE;
+ break;
+ }
+ if (user_email != NULL && strcmp(header, user_email) == 0) {
+ *loop_detected_r = TRUE;
+ break;
+ }
+ headers++;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context)
+{
+ struct act_redirect_transaction *trans;
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ /* Create transaction context */
+ trans = p_new(pool, struct act_redirect_transaction, 1);
+ *tr_context = trans;
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_execute(const struct sieve_action_exec_env *aenv,
+ void *tr_context, bool *keep)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct act_redirect_context *ctx =
+ (struct act_redirect_context *)action->context;
+ struct act_redirect_transaction *trans = tr_context;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ struct mail *mail = (action->mail != NULL ?
+ action->mail : sieve_message_get_mail(msgctx));
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ bool duplicate, loop_detected = FALSE;
+ int ret;
+
+ /*
+ * Prevent mail loops
+ */
+
+ /* Create Message-ID for the message if it has none */
+ trans->msg_id = msgdata->id;
+ if (trans->msg_id == NULL) {
+ pool_t pool = sieve_result_pool(aenv->result);
+ const char *msg_id;
+ if (mail_get_message_id_no_validation(msgdata->mail, &msg_id) > 0)
+ trans->msg_id = p_strdup(pool, msg_id);
+ else {
+ msg_id = sieve_message_get_new_id(svinst);
+ trans->msg_id = trans->new_msg_id = p_strdup(pool, msg_id);
+ }
+ }
+
+ /* Create ID for duplicate database lookup */
+ ret = act_redirect_get_duplicate_id(ctx, aenv, trans->msg_id,
+ &trans->dupeid);
+ if (ret != SIEVE_EXEC_OK)
+ return ret;
+ i_assert(trans->dupeid != NULL);
+
+ /* Check whether we've seen this message before */
+ ret = sieve_action_duplicate_check(aenv, trans->dupeid,
+ strlen(trans->dupeid),
+ &duplicate);
+ if (ret < SIEVE_EXEC_OK) {
+ sieve_result_critical(
+ aenv, "failed to check for duplicate forward",
+ "failed to check for duplicate forward to <%s>%s",
+ smtp_address_encode(ctx->to_address),
+ (ret == SIEVE_EXEC_TEMP_FAILURE ?
+ " (temporaty failure)" : ""));
+ return ret;
+ }
+ if (duplicate) {
+ sieve_result_global_log(
+ aenv, "discarded duplicate forward to <%s>",
+ smtp_address_encode(ctx->to_address));
+ trans->skip_redirect = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Check whether we've seen this message before based on added headers
+ */
+ ret = act_redirect_check_loop_header(aenv, mail, &loop_detected);
+ if (ret != SIEVE_EXEC_OK)
+ return ret;
+ if (loop_detected) {
+ sieve_result_global_log(
+ aenv, "not forwarding message to <%s>: "
+ "the `x-sieve-redirected-from' header indicates a mail loop",
+ smtp_address_encode(ctx->to_address));
+ trans->skip_redirect = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Cancel implicit keep */
+ *keep = FALSE;
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct act_redirect_context *ctx =
+ (struct act_redirect_context *)action->context;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ struct mail *mail = (action->mail != NULL ?
+ action->mail : sieve_message_get_mail(msgctx));
+ struct act_redirect_transaction *trans = tr_context;
+ int ret;
+
+ if (trans->skip_redirect)
+ return SIEVE_EXEC_OK;
+
+ /*
+ * Try to forward the message
+ */
+
+ ret = act_redirect_send(aenv, mail, ctx, trans->new_msg_id);
+ if (ret == SIEVE_EXEC_OK) {
+ /* Mark this message id as forwarded to the specified
+ destination */
+ sieve_action_duplicate_mark(
+ aenv, trans->dupeid, strlen(trans->dupeid),
+ ioloop_time + svinst->redirect_duplicate_period);
+
+ eenv->exec_status->significant_action_executed = TRUE;
+
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("redirect_target",
+ smtp_address_encode(ctx->to_address));
+
+ sieve_result_event_log(aenv, e->event(),
+ "forwarded to <%s>",
+ smtp_address_encode(ctx->to_address));
+
+ /* Indicate that message was successfully forwarded */
+ eenv->exec_status->message_forwarded = TRUE;
+
+ return SIEVE_EXEC_OK;
+ }
+
+ return ret;
+}
diff --git a/pigeonhole/src/lib-sieve/cmd-require.c b/pigeonhole/src/lib-sieve/cmd-require.c
new file mode 100644
index 0000000..93a2a26
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-require.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+/*
+ * Require command
+ *
+ * Syntax
+ * Syntax: require <capabilities: string-list>
+ */
+
+static bool cmd_require_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_require = {
+ .identifier = "require",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_require_validate
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_require_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ bool result = TRUE;
+ struct sieve_ast_argument *arg;
+ struct sieve_command *prev = sieve_command_prev(cmd);
+
+ /* Check valid command placement */
+ if ( !sieve_command_is_toplevel(cmd) ||
+ ( !sieve_command_is_first(cmd) && prev != NULL &&
+ !sieve_command_is(prev, cmd_require) ) )
+ {
+ sieve_command_validate_error(valdtr, cmd,
+ "require commands can only be placed at top level "
+ "at the beginning of the file");
+ return FALSE;
+ }
+
+ /* Check argument and load specified extension(s) */
+
+ arg = cmd->first_positional;
+ if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+ /* Single string */
+ const struct sieve_extension *ext = sieve_validator_extension_load_by_name
+ (valdtr, cmd, arg, sieve_ast_argument_strc(arg));
+
+ if ( ext == NULL ) result = FALSE;
+
+ } else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+ /* String list */
+ struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+ while ( stritem != NULL ) {
+ const struct sieve_extension *ext = sieve_validator_extension_load_by_name
+ (valdtr, cmd, stritem, sieve_ast_strlist_strc(stritem));
+
+ if ( ext == NULL ) result = FALSE;
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+ } else {
+ /* Something else */
+ sieve_argument_validate_error(valdtr, arg,
+ "the require command accepts a single string or string list argument, "
+ "but %s was found",
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ return result;
+}
diff --git a/pigeonhole/src/lib-sieve/cmd-stop.c b/pigeonhole/src/lib-sieve/cmd-stop.c
new file mode 100644
index 0000000..23acadb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmd-stop.c
@@ -0,0 +1,86 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+/*
+ * Stop command
+ *
+ * Syntax
+ * stop
+ */
+
+static bool cmd_stop_generate
+ (const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx ATTR_UNUSED);
+static bool cmd_stop_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_stop = {
+ .identifier = "stop",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_stop_validate,
+ .generate = cmd_stop_generate
+};
+
+/*
+ * Stop operation
+ */
+
+static int opc_stop_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_stop_operation = {
+ .mnemonic = "STOP",
+ .code = SIEVE_OPERATION_STOP,
+ .execute = opc_stop_execute
+};
+
+/*
+ * Command validation
+ */
+
+static bool cmd_stop_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ sieve_command_exit_block_unconditionally(cmd);
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_stop_generate
+(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &cmd_stop_operation);
+
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int opc_stop_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "stop command; end all script execution");
+
+ sieve_interpreter_interrupt(renv->interp);
+
+ return SIEVE_EXEC_OK;
+}
+
diff --git a/pigeonhole/src/lib-sieve/cmp-i-ascii-casemap.c b/pigeonhole/src/lib-sieve/cmp-i-ascii-casemap.c
new file mode 100644
index 0000000..4f0dab4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmp-i-ascii-casemap.c
@@ -0,0 +1,99 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Comparator 'i;ascii-casemap':
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+/*
+ * Forward declarations
+ */
+
+static int cmp_i_ascii_casemap_compare
+ (const struct sieve_comparator *cmp,
+ const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+static bool cmp_i_ascii_casemap_char_match
+ (const struct sieve_comparator *cmp, const char **val1, const char *val1_end,
+ const char **val2, const char *val2_end);
+
+/*
+ * Comparator object
+ */
+
+const struct sieve_comparator_def i_ascii_casemap_comparator = {
+ SIEVE_OBJECT("i;ascii-casemap",
+ &comparator_operand, SIEVE_COMPARATOR_I_ASCII_CASEMAP),
+ .flags =
+ SIEVE_COMPARATOR_FLAG_ORDERING |
+ SIEVE_COMPARATOR_FLAG_EQUALITY |
+ SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH |
+ SIEVE_COMPARATOR_FLAG_PREFIX_MATCH,
+ .compare = cmp_i_ascii_casemap_compare,
+ .char_match = cmp_i_ascii_casemap_char_match,
+ .char_skip = sieve_comparator_octet_skip
+};
+
+/*
+ * Comparator implementation
+ */
+
+static int cmp_i_ascii_casemap_compare(
+ const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char *val1, size_t val1_size, const char *val2, size_t val2_size)
+{
+ int result;
+
+ if ( val1_size == val2_size ) {
+ return strncasecmp(val1, val2, val1_size);
+ }
+
+ if ( val1_size > val2_size ) {
+ result = strncasecmp(val1, val2, val2_size);
+
+ if ( result == 0 ) return 1;
+
+ return result;
+ }
+
+ result = strncasecmp(val1, val2, val1_size);
+
+ if ( result == 0 ) return -1;
+
+ return result;
+}
+
+static bool cmp_i_ascii_casemap_char_match
+ (const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char **val, const char *val_end,
+ const char **key, const char *key_end)
+{
+ const char *val_begin = *val;
+ const char *key_begin = *key;
+
+ while ( i_tolower(**val) == i_tolower(**key) &&
+ *val < val_end && *key < key_end ) {
+ (*val)++;
+ (*key)++;
+ }
+
+ if ( *key < key_end ) {
+ /* Reset */
+ *val = val_begin;
+ *key = key_begin;
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/cmp-i-octet.c b/pigeonhole/src/lib-sieve/cmp-i-octet.c
new file mode 100644
index 0000000..caa46fa
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/cmp-i-octet.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Comparator 'i;octet':
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int cmp_i_octet_compare
+ (const struct sieve_comparator *cmp,
+ const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+static bool cmp_i_octet_char_match
+ (const struct sieve_comparator *cmp, const char **val1, const char *val1_end,
+ const char **val2, const char *val2_end);
+
+/*
+ * Comparator object
+ */
+
+const struct sieve_comparator_def i_octet_comparator = {
+ SIEVE_OBJECT("i;octet",
+ &comparator_operand, SIEVE_COMPARATOR_I_OCTET),
+ .flags =
+ SIEVE_COMPARATOR_FLAG_ORDERING |
+ SIEVE_COMPARATOR_FLAG_EQUALITY |
+ SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH |
+ SIEVE_COMPARATOR_FLAG_PREFIX_MATCH,
+ .compare = cmp_i_octet_compare,
+ .char_match = cmp_i_octet_char_match,
+ .char_skip = sieve_comparator_octet_skip
+};
+
+/*
+ * Comparator implementation
+ */
+
+static int cmp_i_octet_compare(
+ const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char *val1, size_t val1_size, const char *val2, size_t val2_size)
+{
+ int result;
+
+ if ( val1_size == val2_size ) {
+ return memcmp((void *) val1, (void *) val2, val1_size);
+ }
+
+ if ( val1_size > val2_size ) {
+ result = memcmp((void *) val1, (void *) val2, val2_size);
+
+ if ( result == 0 ) return 1;
+
+ return result;
+ }
+
+ result = memcmp((void *) val1, (void *) val2, val1_size);
+
+ if ( result == 0 ) return -1;
+
+ return result;
+}
+
+static bool cmp_i_octet_char_match
+ (const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char **val, const char *val_end,
+ const char **key, const char *key_end)
+{
+ const char *val_begin = *val;
+ const char *key_begin = *key;
+
+ while ( **val == **key && *val < val_end && *key < key_end ) {
+ (*val)++;
+ (*key)++;
+ }
+
+ if ( *key < key_end ) {
+ /* Reset */
+ *val = val_begin;
+ *key = key_begin;
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/ext-encoded-character.c b/pigeonhole/src/lib-sieve/ext-encoded-character.c
new file mode 100644
index 0000000..d9e2b18
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/ext-encoded-character.c
@@ -0,0 +1,271 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension encoded-character
+ * ---------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+
+#include <ctype.h>
+
+/*
+ * Extension
+ */
+
+static bool ext_encoded_character_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def encoded_character_extension = {
+ .name = "encoded-character",
+ .validator_load = ext_encoded_character_validator_load,
+};
+
+/*
+ * Encoded string argument
+ */
+
+bool arg_encoded_string_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def encoded_string_argument = {
+ .identifier = "@encoded-string",
+ .validate = arg_encoded_string_validate
+};
+
+/* Parsing */
+
+static bool _skip_whitespace
+ (const char **in, const char *inend)
+{
+ while ( *in < inend ) {
+ if ( **in == '\r' ) {
+ (*in)++;
+ if ( **in != '\n' )
+ return FALSE;
+ continue;
+ }
+
+ /* (Loose LF is non-standard) */
+ if ( **in != ' ' && **in != '\n' && **in != '\t' )
+ break;
+
+ (*in)++;
+ }
+
+ return TRUE;
+}
+
+static bool _parse_hexint
+(const char **in, const char *inend, int max_digits, unsigned int *result)
+{
+ int digit = 0;
+ *result = 0;
+
+ while ( *in < inend && (max_digits == 0 || digit < max_digits) ) {
+
+ if ( (**in) >= '0' && (**in) <= '9' )
+ *result = ((*result) << 4) + (**in) - ((unsigned int) '0');
+ else if ( (**in) >= 'a' && (**in) <= 'f' )
+ *result = ((*result) << 4) + (**in) - ((unsigned int) 'a') + 0x0a;
+ else if ( (**in) >= 'A' && (**in) <= 'F' )
+ *result = ((*result) << 4) + (**in) - ((unsigned int) 'A') + 0x0a;
+ else
+ return ( digit > 0 );
+
+ (*in)++;
+ digit++;
+ }
+
+ if ( digit == max_digits ) {
+ /* Hex digit _MUST_ end here */
+ if ( (**in >= '0' && **in <= '9') || (**in >= 'a' && **in <= 'f') ||
+ (**in >= 'A' && **in <= 'F') )
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return ( digit > 0 );
+}
+
+static bool _decode_hex
+(const char **in, const char *inend, string_t *result)
+{
+ int values = 0;
+
+ while ( *in < inend ) {
+ unsigned int hexpair;
+
+ if ( !_skip_whitespace(in, inend) ) return FALSE;
+
+ if ( !_parse_hexint(in, inend, 2, &hexpair) ) break;
+
+ str_append_c(result, (unsigned char) hexpair);
+ values++;
+ }
+
+ return ( values > 0 );
+}
+
+static bool _decode_unicode
+(const char **in, const char *inend, string_t *result,
+ unsigned int *error_hex)
+{
+ int values = 0;
+ bool valid = TRUE;
+
+ while ( *in < inend ) {
+ unsigned int unicode_hex;
+
+ if ( !_skip_whitespace(in, inend) ) return FALSE;
+
+ if ( !_parse_hexint(in, inend, 0, &unicode_hex) ) break;
+
+ if ( uni_is_valid_ucs4((unichar_t) unicode_hex) )
+ uni_ucs4_to_utf8_c((unichar_t) unicode_hex, result);
+ else {
+ if ( valid ) *error_hex = unicode_hex;
+ valid = FALSE;
+ }
+ values++;
+ }
+
+ return ( values > 0 );
+}
+
+bool arg_encoded_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ bool result = TRUE;
+ enum { ST_NONE, ST_OPEN, ST_TYPE, ST_CLOSE }
+ state = ST_NONE;
+ string_t *str = sieve_ast_argument_str(*arg);
+ string_t *tmpstr, *newstr = NULL;
+ const char *p, *mark, *strstart, *substart = NULL;
+ const char *strval = (const char *) str_data(str);
+ const char *strend = strval + str_len(str);
+ unsigned int error_hex = 0;
+
+ T_BEGIN {
+ tmpstr = t_str_new(32);
+
+ p = strval;
+ strstart = p;
+ while ( result && p < strend ) {
+ switch ( state ) {
+ /* Normal string */
+ case ST_NONE:
+ if ( *p == '$' ) {
+ substart = p;
+ state = ST_OPEN;
+ }
+ p++;
+ break;
+ /* Parsed '$' */
+ case ST_OPEN:
+ if ( *p == '{' ) {
+ state = ST_TYPE;
+ p++;
+ } else
+ state = ST_NONE;
+ break;
+ /* Parsed '${' */
+ case ST_TYPE:
+ mark = p;
+ /* Scan for 'hex' or 'unicode' */
+ while ( p < strend && i_isalpha(*p) ) p++;
+
+ if ( *p != ':' ) {
+ state = ST_NONE;
+ break;
+ }
+
+ state = ST_CLOSE;
+
+ str_truncate(tmpstr, 0);
+ if ( strncasecmp(mark, "hex", p - mark) == 0 ) {
+ /* Hexadecimal */
+ p++;
+ if ( !_decode_hex(&p, strend, tmpstr) )
+ state = ST_NONE;
+ } else if ( strncasecmp(mark, "unicode", p - mark) == 0 ) {
+ /* Unicode */
+ p++;
+ if ( !_decode_unicode(&p, strend, tmpstr, &error_hex) )
+ state = ST_NONE;
+ } else {
+ /* Invalid encoding */
+ p++;
+ state = ST_NONE;
+ }
+ break;
+ case ST_CLOSE:
+ if ( *p == '}' ) {
+ /* We now know that the substitution is valid */
+
+ if ( error_hex != 0 ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "invalid unicode character 0x%08x in encoded character substitution",
+ error_hex);
+ result = FALSE;
+ break;
+ }
+
+ if ( newstr == NULL ) {
+ newstr = str_new(sieve_ast_pool((*arg)->ast), str_len(str)*2);
+ }
+
+ str_append_data(newstr, strstart, substart-strstart);
+ str_append_str(newstr, tmpstr);
+
+ strstart = p + 1;
+ substart = strstart;
+
+ p++;
+ }
+ state = ST_NONE;
+ }
+ }
+ } T_END;
+
+ if ( !result ) return FALSE;
+
+ if ( newstr != NULL ) {
+ if ( strstart != strend )
+ str_append_data(newstr, strstart, strend-strstart);
+
+ sieve_ast_argument_string_set(*arg, newstr);
+ }
+
+ /* Pass the processed string to a (possible) next layer of processing */
+ return sieve_validator_argument_activate_super
+ (valdtr, cmd, *arg, TRUE);
+}
+
+/*
+ * Extension implementation
+ */
+
+static bool ext_encoded_character_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Override the constant string argument with our own */
+ sieve_validator_argument_override
+ (valdtr, SAT_CONST_STRING, ext, &encoded_string_argument);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/ext-envelope.c b/pigeonhole/src/lib-sieve/ext-envelope.c
new file mode 100644
index 0000000..e889cff
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/ext-envelope.c
@@ -0,0 +1,732 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension envelope
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-message.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def envelope_test;
+const struct sieve_operation_def envelope_operation;
+const struct sieve_extension_def envelope_extension;
+
+/*
+ * Extension
+ */
+
+static bool
+ext_envelope_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+static bool
+ext_envelope_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+static bool
+ext_envelope_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+static int
+ext_envelope_interpreter_run(const struct sieve_extension *this_ext,
+ const struct sieve_runtime_env *renv,
+ void *context, bool deferred);
+
+const struct sieve_extension_def envelope_extension = {
+ .name = "envelope",
+ .interpreter_load = ext_envelope_interpreter_load,
+ .validator_load = ext_envelope_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(envelope_operation)
+};
+const struct sieve_validator_extension
+envelope_validator_extension = {
+ .ext = &envelope_extension,
+ .validate = ext_envelope_validator_validate
+};
+const struct sieve_interpreter_extension
+envelope_interpreter_extension = {
+ .ext_def = &envelope_extension,
+ .run = ext_envelope_interpreter_run
+};
+
+static bool
+ext_envelope_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register new test */
+ sieve_validator_register_command(valdtr, ext, &envelope_test);
+
+ sieve_validator_extension_register(valdtr, ext,
+ &envelope_validator_extension, NULL);
+ return TRUE;
+}
+
+static bool
+ext_envelope_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_interpreter_extension_register(renv->interp, ext,
+ &envelope_interpreter_extension,
+ NULL);
+ return TRUE;
+}
+
+static bool
+ext_envelope_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr,
+ void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ bool required)
+{
+ if (required) {
+ enum sieve_compile_flags flags =
+ sieve_validator_compile_flags(valdtr);
+
+ if ((flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0) {
+ sieve_argument_validate_error(
+ valdtr, require_arg,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static int
+ext_envelope_interpreter_run(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context ATTR_UNUSED, bool deferred)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0) {
+ if (!deferred) {
+ sieve_runtime_error(
+ renv, NULL,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ }
+ return SIEVE_EXEC_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Envelope test
+ *
+ * Syntax
+ * envelope [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
+ * <envelope-part: string-list> <key-list: string-list>
+ */
+
+static bool
+tst_envelope_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+tst_envelope_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_envelope_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+static const struct sieve_command_def envelope_test = {
+ .identifier = "envelope",
+ .type = SCT_TEST,
+ .positional_args= 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_envelope_registered,
+ .validate = tst_envelope_validate,
+ .generate = tst_envelope_generate
+};
+
+/*
+ * Envelope operation
+ */
+
+static bool
+ext_envelope_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+ext_envelope_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def envelope_operation = {
+ .mnemonic = "ENVELOPE",
+ .ext_def = &envelope_extension,
+ .dump = ext_envelope_operation_dump,
+ .execute = ext_envelope_operation_execute
+};
+
+/*
+ * Envelope parts
+ *
+ * FIXME: not available to extensions
+ */
+
+struct sieve_envelope_part {
+ const char *identifier;
+
+ const struct smtp_address *const *(*get_addresses)
+ (const struct sieve_runtime_env *renv);
+ const char * const *(*get_values)
+ (const struct sieve_runtime_env *renv);
+};
+
+static const struct smtp_address *const *
+_from_part_get_addresses(const struct sieve_runtime_env *renv);
+static const char *const *
+_from_part_get_values(const struct sieve_runtime_env *renv);
+static const struct smtp_address *const *
+_to_part_get_addresses(const struct sieve_runtime_env *renv);
+static const char *const *
+_to_part_get_values(const struct sieve_runtime_env *renv);
+static const char *const *
+_auth_part_get_values(const struct sieve_runtime_env *renv);
+
+static const struct sieve_envelope_part _from_part = {
+ "from",
+ _from_part_get_addresses,
+ _from_part_get_values,
+};
+
+static const struct sieve_envelope_part _to_part = {
+ "to",
+ _to_part_get_addresses,
+ _to_part_get_values,
+};
+
+static const struct sieve_envelope_part _auth_part = {
+ "auth",
+ NULL,
+ _auth_part_get_values,
+};
+
+static const struct sieve_envelope_part *_envelope_parts[] = {
+ /* Required */
+ &_from_part, &_to_part,
+
+ /* Non-standard */
+ &_auth_part
+};
+
+static unsigned int _envelope_part_count = N_ELEMENTS(_envelope_parts);
+
+static const struct sieve_envelope_part *
+_envelope_part_find(const char *identifier)
+{
+ unsigned int i;
+
+ for (i = 0; i < _envelope_part_count; i++) {
+ if (strcasecmp(_envelope_parts[i]->identifier,
+ identifier) == 0)
+ return _envelope_parts[i];
+ }
+
+ return NULL;
+}
+
+/* Envelope parts implementation */
+
+static const struct smtp_address *const *
+_from_part_get_addresses(const struct sieve_runtime_env *renv)
+{
+ ARRAY(const struct smtp_address *) envelope_values;
+ const struct smtp_address *address =
+ sieve_message_get_sender(renv->msgctx);
+
+ t_array_init(&envelope_values, 2);
+
+ if (address == NULL)
+ address = smtp_address_create_temp(NULL, NULL);
+ array_append(&envelope_values, &address, 1);
+
+ (void)array_append_space(&envelope_values);
+ return array_idx(&envelope_values, 0);
+}
+
+static const char *const *
+_from_part_get_values(const struct sieve_runtime_env *renv)
+{
+ ARRAY(const char *)envelope_values;
+ const struct smtp_address *address =
+ sieve_message_get_sender(renv->msgctx);
+ const char *value;
+
+ t_array_init(&envelope_values, 2);
+
+ value = "";
+ if (!smtp_address_isnull(address))
+ value = smtp_address_encode(address);
+ array_append(&envelope_values, &value, 1);
+
+ (void)array_append_space(&envelope_values);
+
+ return array_idx(&envelope_values, 0);
+}
+
+static const struct smtp_address *const *
+_to_part_get_addresses(const struct sieve_runtime_env *renv)
+{
+ ARRAY(const struct smtp_address *) envelope_values;
+ const struct smtp_address *address =
+ sieve_message_get_orig_recipient(renv->msgctx);
+
+ if (address != NULL && address->localpart != NULL) {
+ t_array_init(&envelope_values, 2);
+
+ array_append(&envelope_values, &address, 1);
+
+ (void)array_append_space(&envelope_values);
+ return array_idx(&envelope_values, 0);
+ }
+ return NULL;
+}
+
+static const char *const *
+_to_part_get_values(const struct sieve_runtime_env *renv)
+{
+ ARRAY(const char *) envelope_values;
+ const struct smtp_address *address =
+ sieve_message_get_orig_recipient(renv->msgctx);
+
+ t_array_init(&envelope_values, 2);
+
+ if (address != NULL && address->localpart != NULL) {
+ const char *value = smtp_address_encode(address);
+ array_append(&envelope_values, &value, 1);
+ }
+
+ (void)array_append_space(&envelope_values);
+
+ return array_idx(&envelope_values, 0);
+}
+
+static const char *const *
+_auth_part_get_values(const struct sieve_runtime_env *renv)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ ARRAY(const char *) envelope_values;
+
+ t_array_init(&envelope_values, 2);
+
+ if (eenv->msgdata->auth_user != NULL)
+ array_append(&envelope_values, &eenv->msgdata->auth_user, 1);
+
+ (void)array_append_space(&envelope_values);
+
+ return array_idx(&envelope_values, 0);
+}
+
+/*
+ * Envelope address list
+ */
+
+/* Forward declarations */
+
+static int
+sieve_envelope_address_list_next_string_item(struct sieve_stringlist *_strlist,
+ string_t **str_r);
+static int
+sieve_envelope_address_list_next_item(struct sieve_address_list *_addrlist,
+ struct smtp_address *addr_r,
+ string_t **unparsed_r);
+static void
+sieve_envelope_address_list_reset(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct sieve_envelope_address_list {
+ struct sieve_address_list addrlist;
+
+ struct sieve_stringlist *env_parts;
+
+ const struct smtp_address *const *cur_addresses;
+ const char * const *cur_values;
+
+ int value_index;
+};
+
+static struct sieve_address_list *
+sieve_envelope_address_list_create(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *env_parts)
+{
+ struct sieve_envelope_address_list *addrlist;
+
+ addrlist = t_new(struct sieve_envelope_address_list, 1);
+ addrlist->addrlist.strlist.runenv = renv;
+ addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ addrlist->addrlist.strlist.next_item =
+ sieve_envelope_address_list_next_string_item;
+ addrlist->addrlist.strlist.reset = sieve_envelope_address_list_reset;
+ addrlist->addrlist.next_item = sieve_envelope_address_list_next_item;
+ addrlist->env_parts = env_parts;
+
+ return &addrlist->addrlist;
+}
+
+static int
+sieve_envelope_address_list_next_item(struct sieve_address_list *_addrlist,
+ struct smtp_address *addr_r,
+ string_t **unparsed_r)
+{
+ struct sieve_envelope_address_list *addrlist =
+ (struct sieve_envelope_address_list *)_addrlist;
+ const struct sieve_runtime_env *renv = _addrlist->strlist.runenv;
+
+ if (addr_r != NULL)
+ smtp_address_init(addr_r, NULL, NULL);
+ if (unparsed_r != NULL) *unparsed_r = NULL;
+
+ while (addrlist->cur_addresses == NULL &&
+ addrlist->cur_values == NULL) {
+ const struct sieve_envelope_part *epart;
+ string_t *envp_item = NULL;
+ int ret;
+
+ /* Read next header value from source list */
+ if ((ret = sieve_stringlist_next_item(addrlist->env_parts,
+ &envp_item)) <= 0)
+ return ret;
+
+ if (_addrlist->strlist.trace) {
+ sieve_runtime_trace(
+ _addrlist->strlist.runenv, 0,
+ "getting `%s' part from message envelope",
+ str_sanitize(str_c(envp_item), 80));
+ }
+
+ if ((epart=_envelope_part_find(str_c(envp_item))) != NULL) {
+ addrlist->value_index = 0;
+
+ if (epart->get_addresses != NULL) {
+ /* Field contains addresses */
+ addrlist->cur_addresses =
+ epart->get_addresses(renv);
+
+ /* Drop empty list */
+ if (addrlist->cur_addresses != NULL &&
+ addrlist->cur_addresses[0] == NULL)
+ addrlist->cur_addresses = NULL;
+ }
+
+ if (addrlist->cur_addresses == NULL &&
+ epart->get_values != NULL) {
+ /* Field contains something else */
+ addrlist->cur_values = epart->get_values(renv);
+
+ /* Drop empty list */
+ if (addrlist->cur_values != NULL &&
+ addrlist->cur_values[0] == NULL)
+ addrlist->cur_values = NULL;
+ }
+ }
+ }
+
+ /* Return next item */
+ if (addrlist->cur_addresses != NULL) {
+ const struct smtp_address *addr =
+ addrlist->cur_addresses[addrlist->value_index];
+
+ if (addr->localpart == NULL) {
+ /* Null path <> */
+ if (unparsed_r != NULL)
+ *unparsed_r = t_str_new_const("", 0);
+ } else {
+ if (addr_r != NULL)
+ *addr_r = *addr;
+ }
+
+ /* Advance to next value */
+ addrlist->value_index++;
+ if (addrlist->cur_addresses[addrlist->value_index] == NULL) {
+ addrlist->cur_addresses = NULL;
+ addrlist->value_index = 0;
+ }
+ } else {
+ if (unparsed_r != NULL) {
+ const char *value =
+ addrlist->cur_values[addrlist->value_index];
+
+ *unparsed_r = t_str_new_const(value, strlen(value));
+ }
+
+ /* Advance to next value */
+ addrlist->value_index++;
+ if (addrlist->cur_values[addrlist->value_index] == NULL) {
+ addrlist->cur_values = NULL;
+ addrlist->value_index = 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+sieve_envelope_address_list_next_string_item(struct sieve_stringlist *_strlist,
+ string_t **str_r)
+{
+ struct sieve_address_list *addrlist =
+ (struct sieve_address_list *)_strlist;
+ struct smtp_address addr;
+ int ret;
+
+ if ((ret=sieve_envelope_address_list_next_item(addrlist, &addr,
+ str_r)) <= 0)
+ return ret;
+
+ if (addr.localpart != NULL) {
+ const char *addr_str = smtp_address_encode(&addr);
+ if (str_r != NULL)
+ *str_r = t_str_new_const(addr_str, strlen(addr_str));
+ }
+ return 1;
+}
+
+static void
+sieve_envelope_address_list_reset(struct sieve_stringlist *_strlist)
+{
+ struct sieve_envelope_address_list *addrlist =
+ (struct sieve_envelope_address_list *)_strlist;
+
+ sieve_stringlist_reset(addrlist->env_parts);
+ addrlist->cur_addresses = NULL;
+ addrlist->cur_values = NULL;
+ addrlist->value_index = 0;
+}
+
+/*
+ * Command Registration
+ */
+
+static bool
+tst_envelope_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+ sieve_address_parts_link_tags(valdtr, cmd_reg, SIEVE_AM_OPT_ADDRESS_PART);
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static int
+_envelope_part_is_supported(void *context, struct sieve_ast_argument *arg)
+{
+ const struct sieve_envelope_part **not_address =
+ (const struct sieve_envelope_part **) context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ const struct sieve_envelope_part *epart;
+
+ if ((epart=_envelope_part_find(
+ sieve_ast_strlist_strc(arg))) != NULL) {
+ if (epart->get_addresses == NULL) {
+ if (*not_address == NULL)
+ *not_address = epart;
+ }
+ return 1;
+ }
+ return 0;
+ }
+ return 1; /* Can't check at compile time */
+}
+
+static bool
+tst_envelope_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *epart;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_envelope_part *not_address = NULL;
+
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "envelope part", 1,
+ SAAT_STRING_LIST)) {
+ return FALSE;
+ }
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Check whether supplied envelope parts are supported
+ * FIXME: verify dynamic envelope parts at runtime
+ */
+ epart = arg;
+ if (sieve_ast_stringlist_map(&epart, (void *) &not_address,
+ _envelope_part_is_supported) <= 0) {
+ i_assert(epart != NULL);
+ sieve_argument_validate_error(
+ valdtr, epart,
+ "specified envelope part '%s' is not supported by the envelope test",
+ str_sanitize(sieve_ast_strlist_strc(epart), 64));
+ return FALSE;
+ }
+
+ if (not_address != NULL) {
+ struct sieve_ast_argument *addrp_arg =
+ sieve_command_find_argument(tst, &address_part_tag);
+
+ if (addrp_arg != NULL) {
+ sieve_argument_validate_error(
+ valdtr, addrp_arg,
+ "address part ':%s' specified while non-address envelope part '%s' "
+ "is tested with the envelope test",
+ sieve_ast_argument_tag(addrp_arg),
+ not_address->identifier);
+ return FALSE;
+ }
+ }
+
+ arg = sieve_ast_argument_next(arg);
+
+ if (!sieve_validate_positional_argument(valdtr, tst, arg, "key list", 2,
+ SAAT_STRING_LIST))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate(valdtr, tst, arg,
+ &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tst_envelope_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &envelope_operation);
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+ext_envelope_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "ENVELOPE");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ if (sieve_addrmatch_opr_optional_dump(denv, address, NULL) != 0)
+ return FALSE;
+
+ return (sieve_opr_stringlist_dump(denv, address, "envelope part") &&
+ sieve_opr_stringlist_dump(denv, address, "key list"));
+}
+
+/*
+ * Interpretation
+ */
+
+static int
+ext_envelope_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_address_part addrp =
+ SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
+ struct sieve_stringlist *env_part_list, *value_list, *key_list;
+ struct sieve_address_list *addr_list;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read optional operands */
+ if (sieve_addrmatch_opr_optional_read(renv, address, NULL, &ret,
+ &addrp, &mcht, &cmp) < 0)
+ return ret;
+
+ /* Read envelope-part */
+ if ((ret = sieve_opr_stringlist_read(renv, address, "envelope-part",
+ &env_part_list)) <= 0)
+ return ret;
+
+ /* Read key-list */
+ if ((ret = sieve_opr_stringlist_read(renv, address, "key-list",
+ &key_list)) <= 0)
+ return ret;
+
+ /*
+ * Perform test
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "envelope test");
+
+ /* Create value stringlist */
+ addr_list = sieve_envelope_address_list_create(renv, env_part_list);
+ value_list = sieve_address_part_stringlist_create(renv, &addrp,
+ addr_list);
+
+ /* Perform match */
+ if ((match = sieve_match(renv, &mcht, &cmp,
+ value_list, key_list, &ret)) < 0)
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
+
diff --git a/pigeonhole/src/lib-sieve/ext-fileinto.c b/pigeonhole/src/lib-sieve/ext-fileinto.c
new file mode 100644
index 0000000..fb65758
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/ext-fileinto.c
@@ -0,0 +1,225 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension fileinto
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5228
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def fileinto_command;
+const struct sieve_operation_def fileinto_operation;
+const struct sieve_extension_def fileinto_extension;
+
+/*
+ * Extension
+ */
+
+static bool
+ext_fileinto_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+
+const struct sieve_extension_def fileinto_extension = {
+ .name = "fileinto",
+ .validator_load = ext_fileinto_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(fileinto_operation),
+};
+
+static bool
+ext_fileinto_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register new command */
+ sieve_validator_register_command(valdtr, ext, &fileinto_command);
+
+ return TRUE;
+}
+
+/*
+ * Fileinto command
+ *
+ * Syntax:
+ * fileinto <folder: string>
+ */
+
+static bool
+cmd_fileinto_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_fileinto_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+static const struct sieve_command_def fileinto_command = {
+ .identifier = "fileinto",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_fileinto_validate,
+ .generate = cmd_fileinto_generate,
+};
+
+/*
+ * Fileinto operation
+ */
+
+static bool
+ext_fileinto_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+ext_fileinto_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def fileinto_operation = {
+ .mnemonic = "FILEINTO",
+ .ext_def = &fileinto_extension,
+ .dump = ext_fileinto_operation_dump,
+ .execute = ext_fileinto_operation_execute,
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_fileinto_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "folder",
+ 1, SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ /* Check name validity when folder argument is not a variable */
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *folder = sieve_ast_argument_strc(arg), *error;
+
+ if (!sieve_mailbox_check_name(folder, &error)) {
+ sieve_command_validate_error(
+ valdtr, cmd, "fileinto command: "
+ "invalid folder name `%s' specified: %s",
+ str_sanitize(folder, 256), error);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_fileinto_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &fileinto_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+ext_fileinto_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "FILEINTO");
+ sieve_code_descend(denv);
+
+ if (sieve_action_opr_optional_dump(denv, address, NULL) != 0)
+ return FALSE;
+
+ return sieve_opr_string_dump(denv, address, "folder");
+}
+
+/*
+ * Execution
+ */
+
+static int
+ext_fileinto_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ struct sieve_side_effects_list *slist = NULL;
+ string_t *folder;
+ const char *error;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS);
+ int ret = 0;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands (side effects only) */
+ if (sieve_action_opr_optional_read(renv, address, NULL,
+ &ret, &slist) != 0)
+ return ret;
+
+ /* Folder operand */
+ ret = sieve_opr_string_read(renv, address, "folder", &folder);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (trace) {
+ sieve_runtime_trace(renv, 0, "fileinto action");
+ sieve_runtime_trace_descend(renv);
+ }
+
+ if (!sieve_mailbox_check_name(str_c(folder), &error)) {
+ sieve_runtime_error(
+ renv, NULL, "fileinto command: "
+ "invalid folder name `%s' specified: %s",
+ str_c(folder), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if (trace) {
+ sieve_runtime_trace(renv, 0, "store message in mailbox `%s'",
+ str_sanitize(str_c(folder), 80));
+ }
+
+ /* Add action to result */
+ if (sieve_act_store_add_to_result(renv, "fileinto", slist,
+ str_c(folder)) < 0)
+ return SIEVE_EXEC_FAILURE;
+
+ sieve_message_snapshot(renv->msgctx);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/ext-reject.c b/pigeonhole/src/lib-sieve/ext-reject.c
new file mode 100644
index 0000000..649c1f2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/ext-reject.c
@@ -0,0 +1,606 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension reject
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5429
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "message-date.h"
+#include "message-size.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_command_def reject_command;
+static const struct sieve_operation_def reject_operation;
+
+static const struct sieve_command_def ereject_command;
+static const struct sieve_operation_def ereject_operation;
+
+/*
+ * Extensions
+ */
+
+static bool
+ext_reject_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+static int
+ext_reject_interpreter_run(const struct sieve_extension *this_ext,
+ const struct sieve_runtime_env *renv,
+ void *context, bool deferred);
+
+/* Reject */
+
+static bool
+ext_reject_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+static bool
+ext_reject_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_extension_def reject_extension = {
+ .name = "reject",
+ .validator_load = ext_reject_validator_load,
+ .interpreter_load = ext_reject_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATION(reject_operation)
+};
+const struct sieve_validator_extension
+reject_validator_extension = {
+ .ext = &reject_extension,
+ .validate = ext_reject_validator_validate
+};
+const struct sieve_interpreter_extension
+reject_interpreter_extension = {
+ .ext_def = &reject_extension,
+ .run = ext_reject_interpreter_run
+};
+
+static bool
+ext_reject_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register new command */
+ sieve_validator_register_command(valdtr, ext, &reject_command);
+
+ sieve_validator_extension_register(valdtr, ext,
+ &reject_validator_extension, NULL);
+ return TRUE;
+}
+
+static bool
+ext_reject_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_interpreter_extension_register(renv->interp, ext,
+ &reject_interpreter_extension,
+ NULL);
+ return TRUE;
+}
+
+/* EReject */
+
+static bool
+ext_ereject_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+static bool
+ext_ereject_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_extension_def ereject_extension = {
+ .name = "ereject",
+ .validator_load = ext_ereject_validator_load,
+ .interpreter_load = ext_ereject_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATION(ereject_operation)
+};
+const struct sieve_validator_extension
+ereject_validator_extension = {
+ .ext = &ereject_extension,
+ .validate = ext_reject_validator_validate
+};
+const struct sieve_interpreter_extension
+ereject_interpreter_extension = {
+ .ext_def = &ereject_extension,
+ .run = ext_reject_interpreter_run
+};
+
+static bool
+ext_ereject_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register new command */
+ sieve_validator_register_command(valdtr, ext, &ereject_command);
+
+ sieve_validator_extension_register(valdtr, ext,
+ &ereject_validator_extension, NULL);
+ return TRUE;
+}
+
+static bool
+ext_ereject_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_interpreter_extension_register(renv->interp, ext,
+ &ereject_interpreter_extension,
+ NULL);
+ return TRUE;
+}
+
+/* Environment checking */
+
+static bool
+ext_reject_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr,
+ void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ bool required)
+{
+ if (required) {
+ enum sieve_compile_flags flags =
+ sieve_validator_compile_flags(valdtr);
+
+ if ((flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0) {
+ sieve_argument_validate_error(
+ valdtr, require_arg,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static int
+ext_reject_interpreter_run(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context ATTR_UNUSED, bool deferred)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0) {
+ if (!deferred) {
+ sieve_runtime_error(
+ renv, NULL,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ }
+ return SIEVE_EXEC_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Commands
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_reject_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool
+cmd_reject_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+/* Reject command
+ *
+ * Syntax:
+ * reject <reason: string>
+ */
+
+static const struct sieve_command_def reject_command = {
+ .identifier = "reject",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_reject_validate,
+ .generate = cmd_reject_generate
+};
+
+/* EReject command
+ *
+ * Syntax:
+ * ereject <reason: string>
+ */
+
+static const struct sieve_command_def ereject_command = {
+ .identifier = "ereject",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_reject_validate,
+ .generate = cmd_reject_generate,
+};
+
+/*
+ * Operations
+ */
+
+/* Forward declarations */
+
+static bool
+ext_reject_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+ext_reject_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+/* Reject operation */
+
+static const struct sieve_operation_def reject_operation = {
+ .mnemonic = "REJECT",
+ .ext_def = &reject_extension,
+ .dump = ext_reject_operation_dump,
+ .execute = ext_reject_operation_execute
+};
+
+/* EReject operation */
+
+static const struct sieve_operation_def ereject_operation = {
+ .mnemonic = "EREJECT",
+ .ext_def = &ereject_extension,
+ .dump = ext_reject_operation_dump,
+ .execute = ext_reject_operation_execute
+};
+
+/*
+ * Reject action
+ */
+
+static int
+act_reject_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+int act_reject_check_conflict(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_reject_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_reject_start(const struct sieve_action_exec_env *aenv, void **tr_context);
+static int
+act_reject_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep);
+static int
+act_reject_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+
+const struct sieve_action_def act_reject = {
+ .name = "reject",
+ .flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
+ .check_duplicate = act_reject_check_duplicate,
+ .check_conflict = act_reject_check_conflict,
+ .print = act_reject_print,
+ .start = act_reject_start,
+ .execute = act_reject_execute,
+ .commit = act_reject_commit,
+};
+
+struct act_reject_context {
+ const char *reason;
+ bool ereject;
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_reject_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason",
+ 1, SAAT_STRING))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_reject_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ if (sieve_command_is(cmd, reject_command)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &reject_operation);
+ } else {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &ereject_operation);
+ }
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+ext_reject_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+ sieve_code_descend(denv);
+
+ if (sieve_action_opr_optional_dump(denv, address, NULL) != 0)
+ return FALSE;
+
+ return sieve_opr_string_dump(denv, address, "reason");
+}
+
+/*
+ * Interpretation
+ */
+
+static int
+ext_reject_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_operation *oprtn = renv->oprtn;
+ const struct sieve_extension *this_ext = oprtn->ext;
+ struct sieve_side_effects_list *slist = NULL;
+ struct act_reject_context *act;
+ string_t *reason;
+ pool_t pool;
+ int ret;
+
+ /*
+ * Read data
+ */
+
+ /* Optional operands (side effects only) */
+ if (sieve_action_opr_optional_read(renv, address, NULL,
+ &ret, &slist) != 0)
+ return ret;
+
+ /* Read rejection reason */
+ if ((ret = sieve_opr_string_read(renv, address, "reason",
+ &reason)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
+ if (sieve_operation_is(oprtn, ereject_operation))
+ sieve_runtime_trace(renv, 0, "ereject action");
+ else
+ sieve_runtime_trace(renv, 0, "reject action");
+
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "reject message with reason `%s'",
+ str_sanitize(str_c(reason), 64));
+ }
+
+ /* Add reject action to the result */
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_reject_context, 1);
+ act->reason = p_strdup(pool, str_c(reason));
+ act->ereject = sieve_operation_is(oprtn, ereject_operation);
+
+ if (sieve_result_add_action(renv, this_ext,
+ (act->ereject ? "ereject" : "reject"),
+ &act_reject, slist, (void *)act,
+ 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action implementation
+ */
+
+struct act_reject_transaction {
+ bool ignore_reject:1;
+};
+
+static int
+act_reject_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ if (!sieve_action_is_executed(act_other, renv->result)) {
+ sieve_runtime_error(
+ renv, act->location,
+ "duplicate reject/ereject action not allowed "
+ "(previously triggered one was here: %s)",
+ act_other->location);
+ return -1;
+ }
+
+ return 1;
+}
+
+int act_reject_check_conflict(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ if ((act_other->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) > 0) {
+ if (!sieve_action_is_executed(act_other, renv->result)) {
+ sieve_runtime_error(
+ renv, act->location,
+ "reject/ereject action conflicts with other action: "
+ "the %s action (%s) tries to deliver the message",
+ act_other->def->name, act_other->location);
+ return -1;
+ }
+ }
+
+ if ((act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0) {
+ struct act_reject_context *rj_ctx;
+
+ if (!sieve_action_is_executed(act_other, renv->result)) {
+ sieve_runtime_error(
+ renv, act->location,
+ "reject/ereject action conflicts with other action: "
+ "the %s action (%s) also sends a response to the sender",
+ act_other->def->name, act_other->location);
+ return -1;
+ }
+
+ /* Conflicting action was already executed, transform reject
+ * into discard equivalent.
+ */
+ rj_ctx = (struct act_reject_context *)act->context;
+ rj_ctx->reason = NULL;
+ }
+
+ return 0;
+}
+
+static void
+act_reject_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ struct act_reject_context *rj_ctx =
+ (struct act_reject_context *)action->context;
+
+ if (rj_ctx->reason != NULL) {
+ sieve_result_action_printf(
+ rpenv, "reject message with reason: %s",
+ str_sanitize(rj_ctx->reason, 128));
+ } else {
+ sieve_result_action_printf(
+ rpenv,
+ "reject message without sending a response (discard)");
+ }
+
+ *keep = FALSE;
+}
+
+static int
+act_reject_start(const struct sieve_action_exec_env *aenv, void **tr_context)
+{
+ struct act_reject_transaction *trans;
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ /* Create transaction context */
+ trans = p_new(pool, struct act_reject_transaction, 1);
+ *tr_context = trans;
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_reject_execute(const struct sieve_action_exec_env *aenv,
+ void *tr_context, bool *keep)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_reject_context *rj_ctx =
+ (struct act_reject_context *)aenv->action->context;
+ struct act_reject_transaction *trans = tr_context;
+ const struct smtp_address *sender, *recipient;
+
+ sender = sieve_message_get_sender(aenv->msgctx);
+ recipient = sieve_message_get_orig_recipient(aenv->msgctx);
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
+ sieve_result_global_log(
+ aenv, "not sending reject message (skipped)");
+ trans->ignore_reject = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+ if (smtp_address_isnull(recipient)) {
+ sieve_result_global_warning(
+ aenv, "reject action aborted: envelope recipient is <>");
+ trans->ignore_reject = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+ if (rj_ctx->reason == NULL) {
+ sieve_result_global_log(
+ aenv, "not sending reject message "
+ "(would cause second response to sender)");
+ trans->ignore_reject = TRUE;
+ *keep = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+ if (smtp_address_isnull(sender)) {
+ sieve_result_global_log(
+ aenv, "not sending reject message to <>");
+ trans->ignore_reject = TRUE;
+ *keep = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ *keep = FALSE;
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_reject_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_reject_context *rj_ctx =
+ (struct act_reject_context *)aenv->action->context;
+ struct act_reject_transaction *trans = tr_context;
+ const struct smtp_address *sender, *recipient;
+ int ret;
+
+ sender = sieve_message_get_sender(aenv->msgctx);
+ recipient = sieve_message_get_orig_recipient(aenv->msgctx);
+
+ if (trans->ignore_reject)
+ return SIEVE_EXEC_OK;
+
+ if ((ret = sieve_action_reject_mail(aenv, recipient,
+ rj_ctx->reason)) <= 0)
+ return ret;
+
+ eenv->exec_status->significant_action_executed = TRUE;
+
+ struct event_passthrough *e = sieve_action_create_finish_event(aenv);
+
+ sieve_result_event_log(aenv, e->event(),
+ "rejected message from <%s> (%s)",
+ smtp_address_encode(sender),
+ (rj_ctx->ereject ? "ereject" : "reject"));
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/mcht-contains.c b/pigeonhole/src/lib-sieve/mcht-contains.c
new file mode 100644
index 0000000..a9b3190
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/mcht-contains.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':contains'
+ */
+
+#include "lib.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_contains_match_key
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def contains_match_type = {
+ SIEVE_OBJECT("contains",
+ &match_type_operand, SIEVE_MATCH_TYPE_CONTAINS),
+ .validate_context = sieve_match_substring_validate_context,
+ .match_key = mcht_contains_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+/* FIXME: Naive substring match implementation. Should switch to more
+ * efficient algorithm if large values need to be searched (e.g. message body).
+ */
+static int mcht_contains_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size)
+{
+ const struct sieve_comparator *cmp = mctx->comparator;
+ const char *vend = (const char *) val + val_size;
+ const char *kend = (const char *) key + key_size;
+ const char *vp = val;
+ const char *kp = key;
+
+ if ( val_size == 0 )
+ return ( key_size == 0 ? 1 : 0 );
+
+ if ( cmp->def == NULL || cmp->def->char_match == NULL )
+ return 0;
+
+ while ( (vp < vend) && (kp < kend) ) {
+ if ( !cmp->def->char_match(cmp, &vp, vend, &kp, kend) )
+ vp++;
+ }
+
+ return ( kp == kend ? 1 : 0 );
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/mcht-is.c b/pigeonhole/src/lib-sieve/mcht-is.c
new file mode 100644
index 0000000..db374bb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/mcht-is.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':is':
+ */
+
+#include "lib.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_is_match_key
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def is_match_type = {
+ SIEVE_OBJECT("is",
+ &match_type_operand, SIEVE_MATCH_TYPE_IS),
+ .match_key = mcht_is_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+static int mcht_is_match_key
+(struct sieve_match_context *mctx ATTR_UNUSED,
+ const char *val, size_t val_size,
+ const char *key, size_t key_size)
+{
+ if ( val_size == 0 )
+ return ( key_size == 0 ? 1 : 0 );
+
+ if ( mctx->comparator->def != NULL && mctx->comparator->def->compare != NULL )
+ return (mctx->comparator->def->compare(mctx->comparator,
+ val, val_size, key, key_size) == 0 ? 1 : 0 );
+
+ return 0;
+}
+
diff --git a/pigeonhole/src/lib-sieve/mcht-matches.c b/pigeonhole/src/lib-sieve/mcht-matches.c
new file mode 100644
index 0000000..050fce9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/mcht-matches.c
@@ -0,0 +1,440 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':matches'
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_matches_match_key
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size);
+
+/*
+ * Match-type object
+ */
+
+const struct sieve_match_type_def matches_match_type = {
+ SIEVE_OBJECT("matches",
+ &match_type_operand, SIEVE_MATCH_TYPE_MATCHES),
+ .validate_context = sieve_match_substring_validate_context,
+ .match_key = mcht_matches_match_key
+};
+
+/*
+ * Match-type implementation
+ */
+
+/* Quick 'n dirty debug */
+//#define MATCH_DEBUG
+#ifdef MATCH_DEBUG
+#define debug_printf(...) printf ("match debug: " __VA_ARGS__)
+#else
+#define debug_printf(...)
+#endif
+
+/* FIXME: Naive implementation, substitute this with dovecot src/lib/str-find.c
+ */
+static inline bool _string_find(const struct sieve_comparator *cmp,
+ const char **valp, const char *vend, const char **keyp, const char *kend)
+{
+ while ( (*valp < vend) && (*keyp < kend) ) {
+ if ( !cmp->def->char_match(cmp, valp, vend, keyp, kend) )
+ (*valp)++;
+ }
+
+ return (*keyp == kend);
+}
+
+static char _scan_key_section
+ (string_t *section, const char **wcardp, const char *key_end)
+{
+ /* Find next wildcard and resolve escape sequences */
+ str_truncate(section, 0);
+ while ( *wcardp < key_end && **wcardp != '*' && **wcardp != '?') {
+ if ( **wcardp == '\\' ) {
+ (*wcardp)++;
+ }
+ str_append_c(section, **wcardp);
+ (*wcardp)++;
+ }
+
+ /* Record wildcard character or \0 */
+ if ( *wcardp < key_end ) {
+ return **wcardp;
+ }
+
+ i_assert( *wcardp == key_end );
+ return '\0';
+}
+
+static int mcht_matches_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size)
+{
+ const struct sieve_comparator *cmp = mctx->comparator;
+ struct sieve_match_values *mvalues;
+ string_t *mvalue = NULL, *mchars = NULL;
+ string_t *section, *subsection;
+ const char *vend, *kend, *vp, *kp, *wp, *pvp;
+ bool backtrack = FALSE; /* TRUE: match of '?'-connected sections failed */
+ char wcard = '\0'; /* Current wildcard */
+ char next_wcard = '\0'; /* Next widlcard */
+ unsigned int key_offset = 0;
+
+ if ( cmp->def == NULL || cmp->def->char_match == NULL )
+ return 0;
+
+ /* Key sections */
+ section = t_str_new(32); /* Section (after beginning or *) */
+ subsection = t_str_new(32); /* Sub-section (after ?) */
+
+ /* Mark end of value and key */
+ vend = (const char *) val + val_size;
+ kend = (const char *) key + key_size;
+
+ /* Initialize pointers */
+ vp = val; /* Value pointer */
+ kp = key; /* Key pointer */
+ wp = key; /* Wildcard (key) pointer */
+
+ /* Start match values list if requested */
+ if ( (mvalues = sieve_match_values_start(mctx->runenv)) != NULL ) {
+ /* Skip ${0} for now; added when match succeeds */
+ sieve_match_values_add(mvalues, NULL);
+
+ mvalue = t_str_new(32); /* Match value (*) */
+ mchars = t_str_new(32); /* Match characters (.?..?.??) */
+ }
+
+ /* Match the pattern:
+ * <pattern> = <section>*<section>*<section>...
+ * <section> = <sub-section>?<sub-section>?<sub-section>...
+ *
+ * Escape sequences \? and \* need special attention.
+ */
+
+ debug_printf("=== Start ===\n");
+ debug_printf(" key: %s\n", t_strdup_until(key, kend));
+ debug_printf(" value: %s\n", t_strdup_until(val, vend));
+
+ /* Loop until either key or value ends */
+ while (kp < kend && vp < vend ) {
+ const char *needle, *nend;
+
+ if ( !backtrack ) {
+ /* Search the next '*' wildcard in the key string */
+
+ wcard = next_wcard;
+
+ /* Find the needle to look for in the string */
+ key_offset = 0;
+ for (;;) {
+ next_wcard = _scan_key_section(section, &wp, kend);
+
+ if ( wcard == '\0' || str_len(section) > 0 )
+ break;
+
+ if ( next_wcard == '*' ) {
+ break;
+ }
+
+ if ( wp < kend )
+ wp++;
+ else
+ break;
+ key_offset++;
+ }
+
+ debug_printf("found wildcard '%c' at pos [%d]\n",
+ next_wcard, (int) (wp-key));
+
+ if ( mvalues != NULL )
+ str_truncate(mvalue, 0);
+ } else {
+ /* Backtracked; '*' wildcard is retained */
+ debug_printf("backtracked");
+ backtrack = FALSE;
+ }
+
+ /* Determine what we are looking for */
+ needle = str_c(section);
+ nend = PTR_OFFSET(needle, str_len(section));
+
+ debug_printf(" section needle: '%s'\n", t_strdup_until(needle, nend));
+ debug_printf(" section key: '%s'\n", t_strdup_until(kp, kend));
+ debug_printf(" section remnant: '%s'\n", t_strdup_until(wp, kend));
+ debug_printf(" value remnant: '%s'\n", t_strdup_until(vp, vend));
+ debug_printf(" key offset: %d\n", key_offset);
+
+ pvp = vp;
+ if ( next_wcard == '\0' ) {
+ if ( wcard == '\0' ) {
+ /* No current wildcard; match needs to happen right at the beginning */
+ debug_printf("next_wcard = NULL && wcard = NUL; needle should be equal to value.\n");
+
+ if ( (vend - vp) != (nend - needle) ||
+ !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+ debug_printf(" key not equal to value\n");
+ break;
+ }
+
+ } else {
+ const char *qp, *qend;
+ size_t slen;
+
+ /* No more wildcards; find the needle substring at the end of string */
+ debug_printf("next_wcard = NUL; must find needle at end\n");
+
+ /* Check if the value is still large enough */
+ slen = str_len(section);
+ if ( (vp + slen) > vend ) {
+ debug_printf(" wont match: value is too short\n");
+ break;
+ }
+
+ /* Move value pointer to where the needle should be */
+ vp = vend - slen;
+
+ /* Record match values */
+ qend = vp;
+ qp = vp - key_offset;
+
+ if ( mvalues != NULL )
+ str_append_data(mvalue, pvp, qp-pvp);
+
+ /* Compare needle to end of value string */
+ if ( !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+ debug_printf(" match at end failed\n");
+ break;
+ }
+
+ /* Add match values */
+ if ( mvalues != NULL ) {
+ /* Append '*' match value */
+ sieve_match_values_add(mvalues, mvalue);
+
+ /* Append any initial '?' match values */
+ for ( ; qp < qend; qp++ )
+ sieve_match_values_add_char(mvalues, *qp);
+ }
+ }
+
+ /* Finish match */
+ kp = kend;
+ vp = vend;
+
+ debug_printf(" matched end of value\n");
+ break;
+ } else {
+ /* Next wildcard found; match needle before next wildcard */
+
+ const char *prv = NULL; /* Stored value pointer for backtrack */
+ const char *prk = NULL; /* Stored key pointer for backtrack */
+ const char *prw = NULL; /* Stored wildcard pointer for backtrack */
+ const char *chars;
+
+ /* Reset '?' match values */
+ if ( mvalues != NULL )
+ str_truncate(mchars, 0);
+
+ if ( wcard == '\0' ) {
+ /* No current wildcard; match needs to happen right at the beginning */
+ debug_printf("wcard = NUL; needle should be found at the beginning.\n");
+ debug_printf(" begin needle: '%s'\n", t_strdup_until(needle, nend));
+ debug_printf(" begin value: '%s'\n", t_strdup_until(vp, vend));
+
+ if ( !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+ debug_printf(" failed to find needle at beginning\n");
+ break;
+ }
+
+ } else {
+ /* Current wildcard present; match needle between current and next wildcard */
+ debug_printf("wcard != NUL; must find needle at an offset (>= %d).\n",
+ key_offset);
+
+ /* Match may happen at any offset (>= key offset): find substring */
+ vp += key_offset;
+ if ( (vp >= vend) || !_string_find(cmp, &vp, vend, &needle, nend) ) {
+ debug_printf(" failed to find needle at an offset\n");
+ break;
+ }
+
+ prv = vp - str_len(section);
+ prk = kp;
+ prw = wp;
+
+ /* Append match values */
+ if ( mvalues != NULL ) {
+ const char *qend = vp - str_len(section);
+ const char *qp = qend - key_offset;
+
+ /* Append '*' match value */
+ str_append_data(mvalue, pvp, qp-pvp);
+
+ /* Append any initial '?' match values (those that caused the key
+ * offset.
+ */
+ for ( ; qp < qend; qp++ )
+ str_append_c(mchars, *qp);
+ }
+ }
+
+ /* Update wildcard and key pointers for next wildcard scan */
+ if ( wp < kend ) wp++;
+ kp = wp;
+
+ /* Scan successive '?' wildcards */
+ while ( next_wcard == '?' ) {
+ debug_printf("next_wcard = '?'; need to match arbitrary character\n");
+
+ /* Add match value */
+ if ( mvalues != NULL )
+ str_append_c(mchars, *vp);
+
+ vp++;
+
+ /* Scan for next '?' wildcard */
+ next_wcard = _scan_key_section(subsection, &wp, kend);
+ debug_printf("found next wildcard '%c' at pos [%d] (fixed match)\n",
+ next_wcard, (int) (wp-key));
+
+ /* Determine what we are looking for */
+ needle = str_c(subsection);
+ nend = needle + str_len(subsection);
+
+ debug_printf(" sub key: '%s'\n", t_strdup_until(needle, nend));
+ debug_printf(" value remnant: '%s'\n", vp <= vend ? t_strdup_until(vp, vend) : "");
+
+ /* Try matching the needle at fixed position */
+ if ( (needle == nend && next_wcard == '\0' && vp < vend ) ||
+ !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+
+ /* Match failed: now we have a problem. We need to backtrack to the previous
+ * '*' wildcard occurrence and start scanning for the next possible match.
+ */
+
+ debug_printf(" failed fixed match\n");
+
+ /* Start backtrack */
+ if ( prv != NULL && prv + 1 < vend ) {
+ /* Restore pointers */
+ vp = prv;
+ kp = prk;
+ wp = prw;
+
+ /* Skip forward one value character to scan the next possible match */
+ if ( mvalues != NULL )
+ str_append_c(mvalue, *vp);
+ vp++;
+
+ /* Set wildcard state appropriately */
+ wcard = '*';
+ next_wcard = '?';
+
+ /* Backtrack */
+ backtrack = TRUE;
+
+ debug_printf(" BACKTRACK\n");
+ }
+
+ /* Break '?' wildcard scanning loop */
+ break;
+ }
+
+ /* Update wildcard and key pointers for next wildcard scan */
+ if ( wp < kend ) wp++;
+ kp = wp;
+ }
+
+ if ( !backtrack ) {
+ unsigned int i;
+
+ if ( next_wcard == '?' ) {
+ debug_printf("failed to match '?'\n");
+ break;
+ }
+
+ if ( mvalues != NULL ) {
+ if ( prv != NULL )
+ sieve_match_values_add(mvalues, mvalue);
+
+ chars = (const char *) str_data(mchars);
+
+ for ( i = 0; i < str_len(mchars); i++ ) {
+ sieve_match_values_add_char(mvalues, chars[i]);
+ }
+ }
+
+ if ( next_wcard != '*' ) {
+ debug_printf("failed to match at end of string\n");
+ break;
+ }
+ }
+ }
+
+ /* Check whether string ends in a wildcard
+ * (avoid scanning the rest of the string)
+ */
+ if ( kp == kend && next_wcard == '*' ) {
+ /* Add the rest of the string as match value */
+ if ( mvalues != NULL ) {
+ str_truncate(mvalue, 0);
+ str_append_data(mvalue, vp, vend-vp);
+ sieve_match_values_add(mvalues, mvalue);
+ }
+
+ /* Finish match */
+ kp = kend;
+ vp = vend;
+
+ debug_printf("key ends with '*'\n");
+ break;
+ }
+
+ debug_printf("== Loop ==\n");
+ }
+
+ /* Eat away a trailing series of *s */
+ if ( vp == vend ) {
+ while ( kp < kend && *kp == '*' ) kp++;
+ }
+
+ /* By definition, the match is only successful if both value and key pattern
+ * are exhausted.
+ */
+
+ debug_printf("=== Finish ===\n");
+ debug_printf(" result: %s\n", (kp == kend && vp == vend) ? "true" : "false");
+
+ if (kp == kend && vp == vend) {
+ /* Activate new match values after successful match */
+ if ( mvalues != NULL ) {
+ /* Set ${0} */
+ string_t *matched = str_new_const(pool_datastack_create(), val, val_size);
+ sieve_match_values_set(mvalues, 0, matched);
+
+ /* Commit new match values */
+ sieve_match_values_commit(mctx->runenv, &mvalues);
+ }
+ return 1;
+ }
+
+ /* No match; drop collected match values */
+ sieve_match_values_abort(&mvalues);
+ return 0;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/Makefile.am b/pigeonhole/src/lib-sieve/plugins/Makefile.am
new file mode 100644
index 0000000..2a1c054
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/Makefile.am
@@ -0,0 +1,32 @@
+if BUILD_UNFINISHED
+UNFINISHED =
+endif
+
+SUBDIRS = \
+ vacation \
+ subaddress \
+ comparator-i-ascii-numeric \
+ relational \
+ regex \
+ imap4flags \
+ copy \
+ include \
+ body \
+ variables \
+ enotify \
+ notify \
+ environment \
+ mailbox \
+ date \
+ spamvirustest \
+ ihave \
+ editheader \
+ duplicate \
+ index \
+ metadata \
+ mime \
+ special-use \
+ vnd.dovecot \
+ $(UNFINISHED)
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/Makefile.in b/pigeonhole/src/lib-sieve/plugins/Makefile.in
new file mode 100644
index 0000000..8443114
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/Makefile.in
@@ -0,0 +1,719 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@BUILD_UNFINISHED_TRUE@UNFINISHED =
+SUBDIRS = \
+ vacation \
+ subaddress \
+ comparator-i-ascii-numeric \
+ relational \
+ regex \
+ imap4flags \
+ copy \
+ include \
+ body \
+ variables \
+ enotify \
+ notify \
+ environment \
+ mailbox \
+ date \
+ spamvirustest \
+ ihave \
+ editheader \
+ duplicate \
+ index \
+ metadata \
+ mime \
+ special-use \
+ vnd.dovecot \
+ $(UNFINISHED)
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/body/Makefile.am b/pigeonhole/src/lib-sieve/plugins/body/Makefile.am
new file mode 100644
index 0000000..251b8d6
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_body.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tsts = \
+ tst-body.c
+
+libsieve_ext_body_la_SOURCES = \
+ ext-body-common.c \
+ $(tsts) \
+ ext-body.c
+
+noinst_HEADERS = \
+ ext-body-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/body/Makefile.in b/pigeonhole/src/lib-sieve/plugins/body/Makefile.in
new file mode 100644
index 0000000..5d58bbd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/Makefile.in
@@ -0,0 +1,692 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/body
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_body_la_LIBADD =
+am__objects_1 = tst-body.lo
+am_libsieve_ext_body_la_OBJECTS = ext-body-common.lo $(am__objects_1) \
+ ext-body.lo
+libsieve_ext_body_la_OBJECTS = $(am_libsieve_ext_body_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-body-common.Plo \
+ ./$(DEPDIR)/ext-body.Plo ./$(DEPDIR)/tst-body.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_body_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_body_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_body.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tsts = \
+ tst-body.c
+
+libsieve_ext_body_la_SOURCES = \
+ ext-body-common.c \
+ $(tsts) \
+ ext-body.c
+
+noinst_HEADERS = \
+ ext-body-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/body/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/body/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_body.la: $(libsieve_ext_body_la_OBJECTS) $(libsieve_ext_body_la_DEPENDENCIES) $(EXTRA_libsieve_ext_body_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_body_la_OBJECTS) $(libsieve_ext_body_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-body-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-body.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-body.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-body-common.Plo
+ -rm -f ./$(DEPDIR)/ext-body.Plo
+ -rm -f ./$(DEPDIR)/tst-body.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-body-common.Plo
+ -rm -f ./$(DEPDIR)/ext-body.Plo
+ -rm -f ./$(DEPDIR)/tst-body.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.c b/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.c
new file mode 100644
index 0000000..c19940e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.c
@@ -0,0 +1,102 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+
+#include "ext-body-common.h"
+
+
+/*
+ * Body part stringlist
+ */
+
+static int ext_body_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_body_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+
+struct ext_body_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_message_part_data *body_parts;
+ struct sieve_message_part_data *body_parts_iter;
+};
+
+int ext_body_get_part_list
+(const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+ const char * const *content_types, struct sieve_stringlist **strlist_r)
+{
+ static const char * const _no_content_types[] = { "", NULL };
+ struct ext_body_stringlist *strlist;
+ struct sieve_message_part_data *body_parts = NULL;
+ int ret;
+
+ *strlist_r = NULL;
+
+ if ( content_types == NULL ) content_types = _no_content_types;
+
+ switch ( transform ) {
+ case TST_BODY_TRANSFORM_RAW:
+ if ( (ret=sieve_message_body_get_raw(renv, &body_parts)) <= 0 )
+ return ret;
+ break;
+ case TST_BODY_TRANSFORM_CONTENT:
+ if ( (ret=sieve_message_body_get_content
+ (renv, content_types, &body_parts)) <= 0 )
+ return ret;
+ break;
+ case TST_BODY_TRANSFORM_TEXT:
+ if ( (ret=sieve_message_body_get_text(renv, &body_parts)) <= 0 )
+ return ret;
+ break;
+ default:
+ i_unreached();
+ }
+
+ strlist = t_new(struct ext_body_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.next_item = ext_body_stringlist_next_item;
+ strlist->strlist.reset = ext_body_stringlist_reset;
+ strlist->body_parts = body_parts;
+ strlist->body_parts_iter = body_parts;
+
+ *strlist_r = &strlist->strlist;
+ return SIEVE_EXEC_OK;
+}
+
+static int ext_body_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct ext_body_stringlist *strlist =
+ (struct ext_body_stringlist *)_strlist;
+
+ *str_r = NULL;
+
+ if ( strlist->body_parts_iter->content == NULL ) return 0;
+
+ *str_r = t_str_new_const
+ (strlist->body_parts_iter->content, strlist->body_parts_iter->size);
+ strlist->body_parts_iter++;
+ return 1;
+}
+
+static void ext_body_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct ext_body_stringlist *strlist =
+ (struct ext_body_stringlist *)_strlist;
+
+ strlist->body_parts_iter = strlist->body_parts;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.h b/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.h
new file mode 100644
index 0000000..290ca13
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/ext-body-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_BODY_COMMON_H
+#define EXT_BODY_COMMON_H
+
+/*
+ * Types
+ */
+
+enum tst_body_transform {
+ TST_BODY_TRANSFORM_RAW,
+ TST_BODY_TRANSFORM_CONTENT,
+ TST_BODY_TRANSFORM_TEXT
+};
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def body_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def body_test;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def body_operation;
+
+/*
+ * Message body part extraction
+ */
+
+int ext_body_get_part_list
+ (const struct sieve_runtime_env *renv, enum tst_body_transform transform,
+ const char * const *content_types, struct sieve_stringlist **strlist_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/body/ext-body.c b/pigeonhole/src/lib-sieve/plugins/body/ext-body.c
new file mode 100644
index 0000000..27218bd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/ext-body.c
@@ -0,0 +1,54 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension body
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Original CMUSieve implementation by Timo Sirainen
+ * Specification: RFC 5173
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-body-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_body_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def body_extension = {
+ .name = "body",
+ .validator_load = ext_body_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(body_operation)
+};
+
+static bool ext_body_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new test */
+ sieve_validator_register_command(valdtr, ext, &body_test);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/body/tst-body.c b/pigeonhole/src/lib-sieve/plugins/body/tst-body.c
new file mode 100644
index 0000000..f08dd54
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/body/tst-body.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-body-common.h"
+
+/*
+ * Body test
+ *
+ * Syntax
+ * body [COMPARATOR] [MATCH-TYPE] [BODY-TRANSFORM]
+ * <key-list: string-list>
+ */
+
+static bool tst_body_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_body_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_body_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def body_test = {
+ .identifier = "body",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_body_registered,
+ .validate = tst_body_validate,
+ .generate = tst_body_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool ext_body_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int ext_body_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def body_operation = {
+ .mnemonic = "body",
+ .ext_def = &body_extension,
+ .dump = ext_body_operation_dump,
+ .execute = ext_body_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_body_optional {
+ OPT_BODY_TRANSFORM = SIEVE_MATCH_OPT_LAST
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_body_transform_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_body_transform_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def body_raw_tag = {
+ .identifier = "raw",
+ .validate = tag_body_transform_validate,
+ .generate = tag_body_transform_generate
+};
+
+static const struct sieve_argument_def body_content_tag = {
+ .identifier = "content",
+ .validate = tag_body_transform_validate,
+ .generate = tag_body_transform_generate
+};
+
+static const struct sieve_argument_def body_text_tag = {
+ .identifier = "text",
+ .validate = tag_body_transform_validate,
+ .generate = tag_body_transform_generate
+};
+
+/* Argument implementation */
+
+static bool tag_body_transform_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ enum tst_body_transform transform;
+ struct sieve_ast_argument *tag = *arg;
+
+ /* BODY-TRANSFORM:
+ * :raw
+ * / :content <content-types: string-list>
+ * / :text
+ */
+ if ( (bool) cmd->data ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "the :raw, :content and :text arguments for the body test are mutually "
+ "exclusive, but more than one was specified");
+ return FALSE;
+ }
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* :content tag has a string-list argument */
+ if ( sieve_argument_is(tag, body_raw_tag) )
+ transform = TST_BODY_TRANSFORM_RAW;
+
+ else if ( sieve_argument_is(tag, body_text_tag) )
+ transform = TST_BODY_TRANSFORM_TEXT;
+
+ else if ( sieve_argument_is(tag, body_content_tag) ) {
+ /* Check syntax:
+ * :content <content-types: string-list>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+ return FALSE;
+ }
+
+ /* Assign tag parameters */
+ tag->parameters = *arg;
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ transform = TST_BODY_TRANSFORM_CONTENT;
+ } else
+ return FALSE;
+
+ /* Signal the presence of this tag */
+ cmd->data = (void *) TRUE;
+
+ /* Assign context data */
+ tag->argument->data = (void *) transform;
+
+ return TRUE;
+}
+
+/*
+ * Command Registration
+ */
+
+static bool tst_body_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &body_raw_tag, OPT_BODY_TRANSFORM);
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &body_content_tag, OPT_BODY_TRANSFORM);
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &body_text_tag, OPT_BODY_TRANSFORM);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_body_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_body_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &body_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+static bool tag_body_transform_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ enum tst_body_transform transform =
+ POINTER_CAST_TO(arg->argument->data, enum tst_body_transform);
+
+ sieve_binary_emit_byte(cgenv->sblock, transform);
+ sieve_generate_argument_parameters(cgenv, cmd, arg);
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool ext_body_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ unsigned int transform;
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "BODY");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+ < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_BODY_TRANSFORM:
+ if ( !sieve_binary_read_byte(denv->sblock, address, &transform) )
+ return FALSE;
+
+ switch ( transform ) {
+ case TST_BODY_TRANSFORM_RAW:
+ sieve_code_dumpf(denv, "BODY-TRANSFORM: RAW");
+ break;
+ case TST_BODY_TRANSFORM_TEXT:
+ sieve_code_dumpf(denv, "BODY-TRANSFORM: TEXT");
+ break;
+ case TST_BODY_TRANSFORM_CONTENT:
+ sieve_code_dumpf(denv, "BODY-TRANSFORM: CONTENT");
+
+ sieve_code_descend(denv);
+ if ( !sieve_opr_stringlist_dump(denv, address, "content types") )
+ return FALSE;
+ sieve_code_ascend(denv);
+ break;
+ default:
+ return FALSE;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+ };
+
+ return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Interpretation
+ */
+
+static int ext_body_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ unsigned int transform = TST_BODY_TRANSFORM_TEXT;
+ struct sieve_stringlist *ctype_list, *value_list, *key_list;
+ bool mvalues_active;
+ const char * const *content_types = NULL;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ ctype_list = NULL;
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_BODY_TRANSFORM:
+ if ( !sieve_binary_read_byte(renv->sblock, address, &transform) ||
+ transform > TST_BODY_TRANSFORM_TEXT ) {
+ sieve_runtime_trace_error(renv, "invalid body transform type");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( transform == TST_BODY_TRANSFORM_CONTENT &&
+ (ret=sieve_opr_stringlist_read
+ (renv, address, "content-type-list", &ctype_list)) <= 0 )
+ return ret;
+
+ break;
+
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read key-list */
+
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ if ( ctype_list != NULL && sieve_stringlist_read_all
+ (ctype_list, pool_datastack_create(), &content_types) < 0 ) {
+ sieve_runtime_trace_error(renv, "failed to read content-type-list operand");
+ return ctype_list->exec_status;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "body test");
+
+ /* Extract requested parts */
+ if ( (ret=ext_body_get_part_list(renv,
+ (enum tst_body_transform) transform, content_types,&value_list)) <= 0 )
+ return ret;
+
+ /* Disable match values processing as required by RFC */
+ mvalues_active = sieve_match_values_set_enabled(renv, FALSE);
+
+ /* Perform match */
+ match = sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret);
+
+ /* Restore match values processing */
+ (void)sieve_match_values_set_enabled(renv, mvalues_active);
+
+ if ( match < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.am b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.am
new file mode 100644
index 0000000..8e385c5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libsieve_ext_comparator-i-ascii-numeric.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_comparator_i_ascii_numeric_la_SOURCES = \
+ ext-cmp-i-ascii-numeric.c
diff --git a/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in
new file mode 100644
index 0000000..35ef51f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in
@@ -0,0 +1,674 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/comparator-i-ascii-numeric
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_comparator_i_ascii_numeric_la_LIBADD =
+am_libsieve_ext_comparator_i_ascii_numeric_la_OBJECTS = \
+ ext-cmp-i-ascii-numeric.lo
+libsieve_ext_comparator_i_ascii_numeric_la_OBJECTS = \
+ $(am_libsieve_ext_comparator_i_ascii_numeric_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-cmp-i-ascii-numeric.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_comparator_i_ascii_numeric_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_comparator_i_ascii_numeric_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_comparator-i-ascii-numeric.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_comparator_i_ascii_numeric_la_SOURCES = \
+ ext-cmp-i-ascii-numeric.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_comparator-i-ascii-numeric.la: $(libsieve_ext_comparator_i_ascii_numeric_la_OBJECTS) $(libsieve_ext_comparator_i_ascii_numeric_la_DEPENDENCIES) $(EXTRA_libsieve_ext_comparator_i_ascii_numeric_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_comparator_i_ascii_numeric_la_OBJECTS) $(libsieve_ext_comparator_i_ascii_numeric_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-cmp-i-ascii-numeric.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-cmp-i-ascii-numeric.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-cmp-i-ascii-numeric.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
new file mode 100644
index 0000000..20ec38b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/ext-cmp-i-ascii-numeric.c
@@ -0,0 +1,160 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension comparator-i;ascii-numeric
+ * ------------------------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 2244
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-comparators.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include <ctype.h>
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_operand_def my_comparator_operand;
+
+const struct sieve_comparator_def i_ascii_numeric_comparator;
+
+/*
+ * Extension
+ */
+
+static bool ext_cmp_i_ascii_numeric_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def comparator_i_ascii_numeric_extension = {
+ .name = "comparator-i;ascii-numeric",
+ .validator_load = ext_cmp_i_ascii_numeric_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(my_comparator_operand)
+};
+
+static bool ext_cmp_i_ascii_numeric_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ sieve_comparator_register(validator, ext, &i_ascii_numeric_comparator);
+ return TRUE;
+}
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_comparators =
+ SIEVE_EXT_DEFINE_COMPARATOR(i_ascii_numeric_comparator);
+
+static const struct sieve_operand_def my_comparator_operand = {
+ .name = "comparator-i;ascii-numeric",
+ .ext_def = &comparator_i_ascii_numeric_extension,
+ .class = &sieve_comparator_operand_class,
+ .interface = &ext_comparators
+};
+
+/*
+ * Comparator
+ */
+
+/* Forward declarations */
+
+static int cmp_i_ascii_numeric_compare
+ (const struct sieve_comparator *cmp,
+ const char *val1, size_t val1_size, const char *val2, size_t val2_size);
+
+/* Comparator object */
+
+const struct sieve_comparator_def i_ascii_numeric_comparator = {
+ SIEVE_OBJECT("i;ascii-numeric",
+ &my_comparator_operand, 0),
+ .flags =
+ SIEVE_COMPARATOR_FLAG_ORDERING |
+ SIEVE_COMPARATOR_FLAG_EQUALITY,
+ .compare = cmp_i_ascii_numeric_compare
+};
+
+/* Comparator implementation */
+
+static int cmp_i_ascii_numeric_compare
+ (const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char *val, size_t val_size, const char *key, size_t key_size)
+{
+ const char *vend = val + val_size;
+ const char *kend = key + key_size;
+ const char *vp = val;
+ const char *kp = key;
+ int digits, i;
+
+ /* RFC 4790: All input is valid; strings that do not start with a digit
+ * represent positive infinity.
+ */
+ if ( !i_isdigit(*vp) ) {
+ if ( i_isdigit(*kp) ) {
+ /* Value is greater */
+ return 1;
+ }
+ } else {
+ if ( !i_isdigit(*kp) ) {
+ /* Value is less */
+ return -1;
+ }
+ }
+
+ /* Ignore leading zeros */
+
+ while ( *vp == '0' && vp < vend )
+ vp++;
+
+ while ( *kp == '0' && kp < kend )
+ kp++;
+
+ /* Check whether both numbers are equally long in terms of digits */
+
+ digits = 0;
+ while ( vp < vend && kp < kend && i_isdigit(*vp) && i_isdigit(*kp) ) {
+ vp++;
+ kp++;
+ digits++;
+ }
+
+ if ( vp == vend || !i_isdigit(*vp) ) {
+ if ( kp != kend && i_isdigit(*kp) ) {
+ /* Value is less */
+ return -1;
+ }
+ } else {
+ /* Value is greater */
+ return 1;
+ }
+
+ /* Equally long: compare digits */
+
+ vp -= digits;
+ kp -= digits;
+ i = 0;
+ while ( i < digits ) {
+ if ( *vp > *kp )
+ return 1;
+ else if ( *vp < *kp )
+ return -1;
+
+ kp++;
+ vp++;
+ i++;
+ }
+
+ return 0;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/copy/Makefile.am b/pigeonhole/src/lib-sieve/plugins/copy/Makefile.am
new file mode 100644
index 0000000..483032a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/copy/Makefile.am
@@ -0,0 +1,18 @@
+noinst_LTLIBRARIES = libsieve_ext_copy.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_copy_la_SOURCES = \
+ ext-copy.c
+
+public_headers = \
+ sieve-ext-copy.h
+
+headers =
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
diff --git a/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in b/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in
new file mode 100644
index 0000000..9824089
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in
@@ -0,0 +1,735 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/copy
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_copy_la_LIBADD =
+am_libsieve_ext_copy_la_OBJECTS = ext-copy.lo
+libsieve_ext_copy_la_OBJECTS = $(am_libsieve_ext_copy_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-copy.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_copy_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_copy_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_copy.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_copy_la_SOURCES = \
+ ext-copy.c
+
+public_headers = \
+ sieve-ext-copy.h
+
+headers =
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/copy/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/copy/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_copy.la: $(libsieve_ext_copy_la_OBJECTS) $(libsieve_ext_copy_la_DEPENDENCIES) $(EXTRA_libsieve_ext_copy_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_copy_la_OBJECTS) $(libsieve_ext_copy_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-copy.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-copy.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-copy.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/copy/ext-copy.c b/pigeonhole/src/lib-sieve/plugins/copy/ext-copy.c
new file mode 100644
index 0000000..e7db9dd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/copy/ext-copy.c
@@ -0,0 +1,183 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension copy
+ * --------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 3894
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-copy.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def copy_tag;
+static const struct sieve_operand_def copy_side_effect_operand;
+
+/*
+ * Extension
+ */
+
+static bool
+ext_copy_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+
+const struct sieve_extension_def copy_extension = {
+ .name = "copy",
+ .validator_load = ext_copy_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(copy_side_effect_operand),
+};
+
+static bool
+ext_copy_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register copy tag with redirect and fileinto commands and we don't
+ care whether these commands are registered or even whether they will
+ be registered at all. The validator handles either situation
+ gracefully.
+ */
+ sieve_validator_register_external_tag(valdtr, "redirect", ext,
+ &copy_tag, SIEVE_OPT_SIDE_EFFECT);
+ sieve_validator_register_external_tag(valdtr, "fileinto", ext,
+ &copy_tag, SIEVE_OPT_SIDE_EFFECT);
+ return TRUE;
+}
+
+/*
+ * Side effect
+ */
+
+static void
+seff_copy_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+seff_copy_post_execute(const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+ void *tr_context ATTR_UNUSED,
+ void *se_tr_context ATTR_UNUSED, bool *keep);
+
+const struct sieve_side_effect_def copy_side_effect = {
+ SIEVE_OBJECT("copy", &copy_side_effect_operand, 0),
+ .to_action = &act_store,
+ .print = seff_copy_print,
+ .post_execute = seff_copy_post_execute,
+};
+
+/*
+ * Tagged argument
+ */
+
+static bool
+tag_copy_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd);
+static bool
+tag_copy_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd);
+
+static const struct sieve_argument_def copy_tag = {
+ .identifier = "copy",
+ .validate = tag_copy_validate,
+ .generate = tag_copy_generate,
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+ SIEVE_EXT_DEFINE_SIDE_EFFECT(copy_side_effect);
+
+static const struct sieve_operand_def copy_side_effect_operand = {
+ .name = "copy operand",
+ .ext_def = &copy_extension,
+ .class = &sieve_side_effect_operand_class,
+ .interface = &ext_side_effects,
+};
+
+/*
+ * Tag registration
+ */
+
+void sieve_ext_copy_register_tag(struct sieve_validator *valdtr,
+ const struct sieve_extension *copy_ext,
+ const char *command)
+{
+ if (sieve_validator_extension_loaded(valdtr, copy_ext)) {
+ sieve_validator_register_external_tag(
+ valdtr, command, copy_ext, &copy_tag,
+ SIEVE_OPT_SIDE_EFFECT);
+ }
+}
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_copy_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_copy_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ if (sieve_ast_argument_type(arg) != SAAT_TAG)
+ return FALSE;
+
+ sieve_opr_side_effect_emit(cgenv->sblock, sieve_argument_ext(arg),
+ &copy_side_effect);
+ return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+static void
+seff_copy_print(const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ sieve_result_seffect_printf(rpenv, "preserve implicit keep");
+ *keep = TRUE;
+}
+
+static int
+seff_copy_post_execute(const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+ void *tr_context ATTR_UNUSED,
+ void *se_tr_context ATTR_UNUSED, bool *keep)
+{
+ *keep = TRUE;
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/copy/sieve-ext-copy.h b/pigeonhole/src/lib-sieve/plugins/copy/sieve-ext-copy.h
new file mode 100644
index 0000000..faf19dc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/copy/sieve-ext-copy.h
@@ -0,0 +1,21 @@
+#ifndef SIEVE_EXT_COPY_H
+#define SIEVE_EXT_COPY_H
+
+/* sieve_ext_copy_get_extension():
+ * Get the extension struct for the copy extension.
+ */
+static inline const struct sieve_extension *sieve_ext_copy_get_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_get_by_name(svinst, "copy");
+}
+
+/* sieve_ext_copy_register_tag():
+ * Register the :copy tagged argument for a command other than fileinto and
+ * redirect.
+ */
+void sieve_ext_copy_register_tag
+ (struct sieve_validator *valdtr, const struct sieve_extension *copy_ext,
+ const char *command);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/date/Makefile.am b/pigeonhole/src/lib-sieve/plugins/date/Makefile.am
new file mode 100644
index 0000000..bf257ac
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_date.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-date.c
+
+libsieve_ext_date_la_SOURCES = \
+ $(tests) \
+ ext-date-common.c \
+ ext-date.c
+
+noinst_HEADERS = \
+ ext-date-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/date/Makefile.in b/pigeonhole/src/lib-sieve/plugins/date/Makefile.in
new file mode 100644
index 0000000..0bc3ec0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/Makefile.in
@@ -0,0 +1,692 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/date
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_date_la_LIBADD =
+am__objects_1 = tst-date.lo
+am_libsieve_ext_date_la_OBJECTS = $(am__objects_1) ext-date-common.lo \
+ ext-date.lo
+libsieve_ext_date_la_OBJECTS = $(am_libsieve_ext_date_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-date-common.Plo \
+ ./$(DEPDIR)/ext-date.Plo ./$(DEPDIR)/tst-date.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_date_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_date_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_date.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-date.c
+
+libsieve_ext_date_la_SOURCES = \
+ $(tests) \
+ ext-date-common.c \
+ ext-date.c
+
+noinst_HEADERS = \
+ ext-date-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/date/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/date/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_date.la: $(libsieve_ext_date_la_OBJECTS) $(libsieve_ext_date_la_DEPENDENCIES) $(EXTRA_libsieve_ext_date_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_date_la_OBJECTS) $(libsieve_ext_date_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-date-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-date.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-date.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-date-common.Plo
+ -rm -f ./$(DEPDIR)/ext-date.Plo
+ -rm -f ./$(DEPDIR)/tst-date.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-date-common.Plo
+ -rm -f ./$(DEPDIR)/ext-date.Plo
+ -rm -f ./$(DEPDIR)/tst-date.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.c b/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.c
new file mode 100644
index 0000000..7493b87
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.c
@@ -0,0 +1,593 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "str.h"
+#include "iso8601-date.h"
+#include "message-date.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+
+#include "ext-date-common.h"
+
+#include <time.h>
+#include <ctype.h>
+
+struct ext_date_context {
+ time_t current_date;
+ int zone_offset;
+};
+
+/*
+ * Runtime initialization
+ */
+
+static int ext_date_runtime_init
+(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
+{
+ struct ext_date_context *dctx;
+ pool_t pool;
+ struct timeval msg_time;
+ time_t current_date;
+ struct tm *tm;
+ int zone_offset;
+
+ /* Get current time at instance main script is started */
+ sieve_message_context_time(renv->msgctx, &msg_time);
+ current_date = msg_time.tv_sec;
+
+ tm = localtime(&current_date);
+ zone_offset = utc_offset(tm, current_date);
+
+ /* Create context */
+ pool = sieve_message_context_pool(renv->msgctx);
+ dctx = p_new(pool, struct ext_date_context, 1);
+ dctx->current_date = current_date;
+ dctx->zone_offset = zone_offset;
+
+ sieve_message_context_extension_set
+ (renv->msgctx, ext, (void *) dctx);
+ return SIEVE_EXEC_OK;
+}
+
+static struct sieve_interpreter_extension
+date_interpreter_extension = {
+ .ext_def = &date_extension,
+ .run = ext_date_runtime_init
+};
+
+bool ext_date_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ /* Register runtime hook to obtain stript start timestamp */
+ if ( renv->msgctx == NULL ||
+ sieve_message_context_extension_get(renv->msgctx, ext) == NULL ) {
+ sieve_interpreter_extension_register
+ (renv->interp, ext, &date_interpreter_extension, NULL);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone
+(const char *zone, int *zone_offset_r)
+{
+ const unsigned char *str = (const unsigned char *) zone;
+ size_t len = strlen(zone);
+
+ if (len == 5 && (*str == '+' || *str == '-')) {
+ int offset;
+
+ if (!i_isdigit(str[1]) || !i_isdigit(str[2]) ||
+ !i_isdigit(str[3]) || !i_isdigit(str[4]))
+ return FALSE;
+
+ offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60 +
+ (str[3]-'0') * 10 + (str[4]-'0');
+
+ if ( zone_offset_r != NULL )
+ *zone_offset_r = *str == '+' ? offset : -offset;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date
+(const struct sieve_runtime_env *renv, int *zone_offset_r)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_date_context *dctx = (struct ext_date_context *)
+ sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+ if ( dctx == NULL ) {
+ ext_date_runtime_init(this_ext, renv, NULL, FALSE);
+ dctx = (struct ext_date_context *)
+ sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+ i_assert(dctx != NULL);
+ }
+
+ /* Read script start timestamp from message context */
+
+ if ( zone_offset_r != NULL )
+ *zone_offset_r = dctx->zone_offset;
+
+ return dctx->current_date;
+}
+
+/*
+ * Date parts
+ */
+
+/* "year" => the year, "0000" .. "9999".
+ */
+
+static const char *ext_date_year_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part year_date_part = {
+ "year",
+ ext_date_year_part_get
+};
+
+/* "month" => the month, "01" .. "12".
+ */
+
+static const char *ext_date_month_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part month_date_part = {
+ "month",
+ ext_date_month_part_get
+};
+
+/* "day" => the day, "01" .. "31".
+ */
+
+static const char *ext_date_day_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part day_date_part = {
+ "day",
+ ext_date_day_part_get
+};
+
+/* "date" => the date in "yyyy-mm-dd" format.
+ */
+
+static const char *ext_date_date_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part date_date_part = {
+ "date",
+ ext_date_date_part_get
+};
+
+/* "julian" => the Modified Julian Day, that is, the date
+ * expressed as an integer number of days since
+ * 00:00 UTC on November 17, 1858 (using the Gregorian
+ * calendar). This corresponds to the regular
+ * Julian Day minus 2400000.5. Sample routines to
+ * convert to and from modified Julian dates are
+ * given in Appendix A.
+ */
+
+static const char *ext_date_julian_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part julian_date_part = {
+ "julian",
+ ext_date_julian_part_get
+};
+
+/* "hour" => the hour, "00" .. "23".
+ */
+static const char *ext_date_hour_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part hour_date_part = {
+ "hour",
+ ext_date_hour_part_get
+};
+
+/* "minute" => the minute, "00" .. "59".
+ */
+static const char *ext_date_minute_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part minute_date_part = {
+ "minute",
+ ext_date_minute_part_get
+};
+
+/* "second" => the second, "00" .. "60".
+ */
+static const char *ext_date_second_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part second_date_part = {
+ "second",
+ ext_date_second_part_get
+};
+
+/* "time" => the time in "hh:mm:ss" format.
+ */
+static const char *ext_date_time_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part time_date_part = {
+ "time",
+ ext_date_time_part_get
+};
+
+/* "iso8601" => the date and time in restricted ISO 8601 format.
+ */
+static const char *ext_date_iso8601_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part iso8601_date_part = {
+ "iso8601",
+ ext_date_iso8601_part_get
+};
+
+/* "std11" => the date and time in a format appropriate
+ * for use in a Date: header field [RFC2822].
+ */
+static const char *ext_date_std11_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part std11_date_part = {
+ "std11",
+ ext_date_std11_part_get
+};
+
+/* "zone" => the time zone in use. If the user specified a
+ * time zone with ":zone", "zone" will
+ * contain that value. If :originalzone is specified
+ * this value will be the original zone specified
+ * in the date-time value. If neither argument is
+ * specified the value will be the server's default
+ * time zone in offset format "+hhmm" or "-hhmm". An
+ * offset of 0 (Zulu) always has a positive sign.
+ */
+static const char *ext_date_zone_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part zone_date_part = {
+ "zone",
+ ext_date_zone_part_get
+};
+
+/* "weekday" => the day of the week expressed as an integer between
+ * "0" and "6". "0" is Sunday, "1" is Monday, etc.
+ */
+static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part weekday_date_part = {
+ "weekday",
+ ext_date_weekday_part_get
+};
+
+/*
+ * Date part extraction
+ */
+
+static const struct ext_date_part *date_parts[] = {
+ &year_date_part, &month_date_part, &day_date_part, &date_date_part,
+ &julian_date_part, &hour_date_part, &minute_date_part, &second_date_part,
+ &time_date_part, &iso8601_date_part, &std11_date_part, &zone_date_part,
+ &weekday_date_part
+};
+
+unsigned int date_parts_count = N_ELEMENTS(date_parts);
+
+const struct ext_date_part *ext_date_part_find(const char *part)
+{
+ unsigned int i;
+
+ for ( i = 0; i < date_parts_count; i++ ) {
+ if ( strcasecmp(date_parts[i]->identifier, part) == 0 ) {
+ return date_parts[i];
+ }
+ }
+
+ return NULL;
+}
+
+const char *ext_date_part_extract
+(const struct ext_date_part *dpart, struct tm *tm, int zone_offset)
+{
+ if ( dpart == NULL || dpart->get_string == NULL )
+ return NULL;
+
+ return dpart->get_string(tm, zone_offset);
+}
+
+/*
+ * Date part implementations
+ */
+
+static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const char *weekday_names[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char *ext_date_year_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%04d", tm->tm_year + 1900);
+}
+
+static const char *ext_date_month_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d", tm->tm_mon + 1);
+}
+
+static const char *ext_date_day_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d", tm->tm_mday);
+}
+
+static const char *ext_date_date_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%04d-%02d-%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
+}
+
+static const char *ext_date_julian_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ int year = tm->tm_year+1900;
+ int month = tm->tm_mon+1;
+ int day = tm->tm_mday;
+ int c, ya, jd;
+
+ /* Modified from RFC 5260 Appendix A (refer to Errata) */
+
+ if ( month > 2 )
+ month -= 3;
+ else {
+ month += 9;
+ year--;
+ }
+
+ c = year / 100;
+ ya = year - c * 100;
+
+ jd = c * 146097 / 4 + ya * 1461 / 4 + (month * 153 + 2) / 5 + day + 1721119;
+
+ return t_strdup_printf("%d", jd - 2400001);
+}
+
+static const char *ext_date_hour_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d", tm->tm_hour);
+}
+
+static const char *ext_date_minute_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d", tm->tm_min);
+}
+
+static const char *ext_date_second_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d", tm->tm_sec);
+}
+
+static const char *ext_date_time_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static const char *ext_date_iso8601_part_get
+(struct tm *tm, int zone_offset)
+{
+ /* From RFC: `The restricted ISO 8601 format is specified by the date-time
+ * ABNF production given in [RFC3339], Section 5.6, with the added
+ * restrictions that the letters "T" and "Z" MUST be in upper case, and
+ * a time zone offset of zero MUST be represented by "Z" and not "+00:00".
+ */
+ if ( zone_offset == 0 )
+ zone_offset = INT_MAX;
+
+ return iso8601_date_create_tm(tm, zone_offset);
+}
+
+
+static const char *ext_date_std11_part_get
+(struct tm *tm, int zone_offset)
+{
+ return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %s",
+ weekday_names[tm->tm_wday],
+ tm->tm_mday,
+ month_names[tm->tm_mon],
+ tm->tm_year+1900,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ ext_date_zone_part_get(tm, zone_offset));
+}
+
+static const char *ext_date_zone_part_get
+(struct tm *tm ATTR_UNUSED, int zone_offset)
+{
+ bool negative;
+ int offset = zone_offset;
+
+ if (zone_offset >= 0)
+ negative = FALSE;
+ else {
+ negative = TRUE;
+ offset = -offset;
+ }
+
+ return t_strdup_printf
+ ("%c%02d%02d", negative ? '-' : '+', offset / 60, offset % 60);
+}
+
+static const char *ext_date_weekday_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+ return t_strdup_printf("%d", tm->tm_wday);
+}
+
+/*
+ * Date stringlist
+ */
+
+/* Forward declarations */
+
+static int ext_date_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_date_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct ext_date_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_stringlist *field_values;
+ int time_zone;
+ const struct ext_date_part *date_part;
+
+ time_t local_time;
+ int local_zone;
+
+ bool read:1;
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+ int time_zone, const struct ext_date_part *dpart)
+{
+ struct ext_date_stringlist *strlist;
+
+ strlist = t_new(struct ext_date_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = ext_date_stringlist_next_item;
+ strlist->strlist.reset = ext_date_stringlist_reset;
+ strlist->field_values = field_values;
+ strlist->time_zone = time_zone;
+ strlist->date_part = dpart;
+
+ strlist->local_time = ext_date_get_current_date(renv, &strlist->local_zone);
+
+ return &strlist->strlist;
+}
+
+/* Stringlist implementation */
+
+static int ext_date_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct ext_date_stringlist *strlist =
+ (struct ext_date_stringlist *) _strlist;
+ bool got_date = FALSE;
+ time_t date_value;
+ const char *part_value = NULL;
+ int original_zone;
+
+ /* Check whether the item was already read */
+ if ( strlist->read ) return 0;
+
+ if ( strlist->field_values != NULL ) {
+ string_t *hdr_item;
+ const char *header_value, *date_string;
+ int ret;
+
+ /* Use header field value */
+
+ /* Read first */
+ if ( (ret=sieve_stringlist_next_item(strlist->field_values, &hdr_item))
+ <= 0 )
+ return ret;
+
+ /* Extract the date string value */
+
+ header_value = str_c(hdr_item);
+ date_string = strrchr(header_value, ';');
+
+ if ( date_string == NULL ) {
+ /* Direct header value */
+ date_string = header_value;
+ } else {
+ /* Delimited by ';', e.g. a Received: header */
+ date_string++;
+ }
+
+ /* Parse the date value */
+ if ( message_date_parse((const unsigned char *) date_string,
+ strlen(date_string), &date_value, &original_zone) ) {
+ got_date = TRUE;
+ }
+ } else {
+ /* Use time stamp recorded at the time the script first started */
+ date_value = strlist->local_time;
+ original_zone = strlist->local_zone;
+ got_date = TRUE;
+ }
+
+ if ( got_date ) {
+ int wanted_zone;
+ struct tm *date_tm;
+
+ /* Apply wanted timezone */
+
+ switch ( strlist->time_zone ) {
+ case EXT_DATE_TIMEZONE_LOCAL:
+ wanted_zone = strlist->local_zone;
+ break;
+ case EXT_DATE_TIMEZONE_ORIGINAL:
+ wanted_zone = original_zone;
+ break;
+ default:
+ wanted_zone = strlist->time_zone;
+ }
+
+ date_value += wanted_zone * 60;
+
+ /* Convert timestamp to struct tm */
+
+ if ( (date_tm=gmtime(&date_value)) != NULL ) {
+ /* Extract the date part */
+ part_value = ext_date_part_extract
+ (strlist->date_part, date_tm, wanted_zone);
+ }
+ }
+
+ strlist->read = TRUE;
+
+ if ( part_value == NULL )
+ return 0;
+
+ *str_r = t_str_new_const(part_value, strlen(part_value));
+ return 1;
+}
+
+static void ext_date_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct ext_date_stringlist *strlist =
+ (struct ext_date_stringlist *) _strlist;
+
+ if ( strlist->field_values != NULL )
+ sieve_stringlist_reset(strlist->field_values);
+ strlist->read = FALSE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.h b/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.h
new file mode 100644
index 0000000..116af3e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/ext-date-common.h
@@ -0,0 +1,80 @@
+#ifndef EXT_DATE_COMMON_H
+#define EXT_DATE_COMMON_H
+
+#include "sieve-common.h"
+
+#include <time.h>
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def date_extension;
+
+bool ext_date_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED);
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def date_test;
+extern const struct sieve_command_def currentdate_test;
+
+/*
+ * Operations
+ */
+
+enum ext_date_opcode {
+ EXT_DATE_OPERATION_DATE,
+ EXT_DATE_OPERATION_CURRENTDATE
+};
+
+extern const struct sieve_operation_def date_operation;
+extern const struct sieve_operation_def currentdate_operation;
+
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone(const char *zone, int *zone_offset_r);
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date
+ (const struct sieve_runtime_env *renv, int *zone_offset_r);
+
+/*
+ * Date part
+ */
+
+struct ext_date_part {
+ const char *identifier;
+
+ const char *(*get_string)(struct tm *tm, int zone_offset);
+};
+
+const struct ext_date_part *ext_date_part_find(const char *part);
+
+const char *ext_date_part_extract
+ (const struct ext_date_part *dpart, struct tm *tm, int zone_offset);
+
+/*
+ * Date stringlist
+ */
+
+enum ext_date_timezone_special {
+ EXT_DATE_TIMEZONE_LOCAL = 100,
+ EXT_DATE_TIMEZONE_ORIGINAL = 101
+};
+
+struct sieve_stringlist *ext_date_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *field_values,
+ int time_zone, const struct ext_date_part *dpart);
+
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/date/ext-date.c b/pigeonhole/src/lib-sieve/plugins/date/ext-date.c
new file mode 100644
index 0000000..3880d82
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/ext-date.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension date
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5260
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-date-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_date_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_operation_def *ext_date_operations[] = {
+ &date_operation,
+ &currentdate_operation
+};
+
+const struct sieve_extension_def date_extension = {
+ .name = "date",
+ .validator_load = ext_date_validator_load,
+ .interpreter_load = ext_date_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_date_operations)
+};
+
+static bool ext_date_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new test */
+ sieve_validator_register_command(valdtr, ext, &date_test);
+ sieve_validator_register_command(valdtr, ext, &currentdate_test);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/date/tst-date.c b/pigeonhole/src/lib-sieve/plugins/date/tst-date.c
new file mode 100644
index 0000000..60ee92c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/date/tst-date.c
@@ -0,0 +1,496 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-date-common.h"
+
+#include <time.h>
+
+/*
+ * Tests
+ */
+
+static bool tst_date_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_date_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Date test
+ *
+ * Syntax:
+ * date [<":zone" <time-zone: string>> / ":originalzone"]
+ * [COMPARATOR] [MATCH-TYPE] <header-name: string>
+ * <date-part: string> <key-list: string-list>
+ */
+
+static bool tst_date_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def date_test = {
+ .identifier = "date",
+ .type = SCT_TEST,
+ .positional_args = 3,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_date_registered,
+ .validate = tst_date_validate,
+ .generate = tst_date_generate
+};
+
+/* Currentdate test
+ *
+ * Syntax:
+ * currentdate [":zone" <time-zone: string>]
+ * [COMPARATOR] [MATCH-TYPE]
+ * <date-part: string> <key-list: string-list>
+ */
+
+static bool tst_currentdate_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def currentdate_test = {
+ .identifier = "currentdate",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_currentdate_registered,
+ .validate = tst_date_validate,
+ .generate = tst_date_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_zone_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_zone_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def date_zone_tag = {
+ .identifier = "zone",
+ .validate = tag_zone_validate,
+ .generate = tag_zone_generate
+};
+
+static const struct sieve_argument_def date_originalzone_tag = {
+ .identifier = "originalzone",
+ .validate = tag_zone_validate,
+ .generate = tag_zone_generate
+};
+
+/*
+ * Date operation
+ */
+
+static bool tst_date_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_date_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def date_operation = {
+ .mnemonic = "DATE",
+ .ext_def = &date_extension,
+ .code = EXT_DATE_OPERATION_DATE,
+ .dump = tst_date_operation_dump,
+ .execute = tst_date_operation_execute
+};
+
+const struct sieve_operation_def currentdate_operation = {
+ .mnemonic = "CURRENTDATE",
+ .ext_def = &date_extension,
+ .code = EXT_DATE_OPERATION_CURRENTDATE,
+ .dump = tst_date_operation_dump,
+ .execute = tst_date_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_date_optional {
+ OPT_DATE_ZONE = SIEVE_AM_OPT_LAST,
+ OPT_DATE_LAST
+};
+
+/*
+ * Tag implementation
+ */
+
+static bool tag_zone_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ if ( (bool) cmd->data ) {
+ if ( sieve_command_is(cmd, date_test) ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "multiple :zone or :originalzone arguments specified for "
+ "the currentdate test");
+ } else {
+ sieve_argument_validate_error(valdtr, *arg,
+ "multiple :zone arguments specified for the currentdate test");
+ }
+ return FALSE;
+ }
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* :content tag has a string-list argument */
+ if ( sieve_argument_is(tag, date_zone_tag) ) {
+
+ /* Check syntax:
+ * :zone <time-zone: string>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+ return FALSE;
+ }
+
+ /* Check it */
+ if ( sieve_argument_is_string_literal(*arg) ) {
+ const char *zone = sieve_ast_argument_strc(*arg);
+
+ if ( !ext_date_parse_timezone(zone, NULL) ) {
+ sieve_argument_validate_warning(valdtr, *arg,
+ "specified :zone argument '%s' is not a valid timezone",
+ str_sanitize(zone, 40));
+ }
+ }
+
+ /* Assign tag parameters */
+ tag->parameters = *arg;
+ *arg = sieve_ast_arguments_detach(*arg,1);
+ }
+
+ cmd->data = (void *) TRUE;
+
+ return TRUE;
+}
+
+/*
+ * Test registration
+ */
+
+static bool tst_date_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &date_zone_tag, OPT_DATE_ZONE);
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &date_originalzone_tag, OPT_DATE_ZONE);
+
+ return TRUE;
+}
+
+static bool tst_currentdate_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &date_zone_tag, OPT_DATE_ZONE);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_date_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ unsigned int arg_offset = 0 ;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ /* Check header name */
+
+ if ( sieve_command_is(tst, date_test) ) {
+ arg_offset = 1;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "header name", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Check date part */
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "date part", arg_offset + 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ if ( sieve_argument_is_string_literal(arg) ) {
+ const char * part = sieve_ast_argument_strc(arg);
+
+ if ( ext_date_part_find(part) == NULL ) {
+ sieve_argument_validate_warning
+ (valdtr, arg, "specified date part `%s' is not known",
+ str_sanitize(part, 80));
+ }
+ }
+
+ arg = sieve_ast_argument_next(arg);
+
+ /* Check key list */
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", arg_offset + 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_date_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ if ( sieve_command_is(tst, date_test) )
+ sieve_operation_emit(cgenv->sblock, tst->ext, &date_operation);
+ else if ( sieve_command_is(tst, currentdate_test) )
+ sieve_operation_emit(cgenv->sblock, tst->ext, &currentdate_operation);
+ else
+ i_unreached();
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+static bool tag_zone_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ if ( arg->parameters == NULL ) {
+ sieve_opr_omitted_emit(cgenv->sblock);
+ return TRUE;
+ }
+
+ return sieve_generate_argument_parameters(cgenv, cmd, arg);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_date_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ const struct sieve_operation *op = denv->oprtn;
+
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_message_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_DATE_ZONE:
+ if ( !sieve_opr_string_dump_ex(denv, address, "zone", "ORIGINAL") )
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ if ( sieve_operation_is(op, date_operation) &&
+ !sieve_opr_string_dump(denv, address, "header name") )
+ return FALSE;
+
+ return
+ sieve_opr_string_dump(denv, address, "date part") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+
+/*
+ * Code execution
+ */
+
+static int tst_date_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *op = renv->oprtn;
+ int opt_code = 0;
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ ARRAY_TYPE(sieve_message_override) svmos;
+ string_t *date_part = NULL, *zone = NULL;
+ struct sieve_stringlist *hdr_list = NULL, *hdr_value_list;
+ struct sieve_stringlist *value_list, *key_list;
+ bool zone_specified = FALSE, zone_literal = TRUE;
+ const struct ext_date_part *dpart;
+ int time_zone;
+ int match, ret;
+
+ /* Read optional operands */
+ for (;;) {
+ int opt;
+
+ /* Optional operands */
+ i_zero(&svmos);
+ if ( (opt=sieve_message_opr_optional_read
+ (renv, address, &opt_code, &ret, NULL, &mcht, &cmp, &svmos)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_DATE_ZONE:
+ if ( (ret=sieve_opr_string_read_ex
+ (renv, address, "zone", TRUE, &zone, &zone_literal)) <= 0 )
+ return ret;
+
+ zone_specified = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ if ( sieve_operation_is(op, date_operation) ) {
+ /* Read header name as stringlist */
+ if ( (ret=sieve_opr_stringlist_read
+ (renv, address, "header-name", &hdr_list)) <= 0 )
+ return ret;
+ }
+
+ /* Read date part */
+ if ( (ret=sieve_opr_string_read(renv, address, "date-part", &date_part))
+ <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ /* Determine what time zone to use in the result */
+ if ( !zone_specified ) {
+ time_zone = EXT_DATE_TIMEZONE_LOCAL;
+ } else if ( zone == NULL ) {
+ time_zone = EXT_DATE_TIMEZONE_ORIGINAL;
+ } else if ( !ext_date_parse_timezone(str_c(zone), &time_zone) ) {
+ if ( !zone_literal )
+ sieve_runtime_warning(renv, NULL,
+ "specified :zone argument `%s' is not a valid timezone "
+ "(using local zone)", str_sanitize(str_c(zone), 40));
+ time_zone = EXT_DATE_TIMEZONE_LOCAL;
+ }
+
+ if ( (dpart=ext_date_part_find(str_c(date_part))) == NULL ) {
+ sieve_runtime_warning(renv, NULL,
+ "specified date part argument `%s' is not known",
+ str_sanitize(str_c(date_part), 40));
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ /*
+ * Perform test
+ */
+
+ if ( sieve_operation_is(op, date_operation) ) {
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "date test");
+
+ /* Get header */
+ sieve_runtime_trace_descend(renv);
+ if ( (ret=sieve_message_get_header_fields
+ (renv, hdr_list, &svmos, FALSE, &hdr_value_list)) <= 0 )
+ return ret;
+ sieve_runtime_trace_ascend(renv);
+
+ /* Create value stringlist */
+ value_list = ext_date_stringlist_create
+ (renv, hdr_value_list, time_zone, dpart);
+
+ } else if ( sieve_operation_is(op, currentdate_operation) ) {
+ /* Use time stamp recorded at the time the script first started */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "currentdatedate test");
+
+ /* Create value stringlist */
+ value_list = ext_date_stringlist_create(renv, NULL, time_zone, dpart);
+ } else {
+ i_unreached();
+ }
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.am b/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.am
new file mode 100644
index 0000000..7469b98
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.am
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libsieve_ext_duplicate.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-duplicate.c
+
+extensions = \
+ ext-duplicate.c
+
+libsieve_ext_duplicate_la_SOURCES = \
+ $(tests) \
+ $(extensions) \
+ ext-duplicate-common.c
+
+noinst_HEADERS = \
+ ext-duplicate-common.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in b/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in
new file mode 100644
index 0000000..d29f834
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in
@@ -0,0 +1,697 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/duplicate
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_duplicate_la_LIBADD =
+am__objects_1 = tst-duplicate.lo
+am__objects_2 = ext-duplicate.lo
+am_libsieve_ext_duplicate_la_OBJECTS = $(am__objects_1) \
+ $(am__objects_2) ext-duplicate-common.lo
+libsieve_ext_duplicate_la_OBJECTS = \
+ $(am_libsieve_ext_duplicate_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-duplicate-common.Plo \
+ ./$(DEPDIR)/ext-duplicate.Plo ./$(DEPDIR)/tst-duplicate.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_duplicate_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_duplicate_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_duplicate.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-duplicate.c
+
+extensions = \
+ ext-duplicate.c
+
+libsieve_ext_duplicate_la_SOURCES = \
+ $(tests) \
+ $(extensions) \
+ ext-duplicate-common.c
+
+noinst_HEADERS = \
+ ext-duplicate-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/duplicate/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/duplicate/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_duplicate.la: $(libsieve_ext_duplicate_la_OBJECTS) $(libsieve_ext_duplicate_la_DEPENDENCIES) $(EXTRA_libsieve_ext_duplicate_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_duplicate_la_OBJECTS) $(libsieve_ext_duplicate_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-duplicate-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-duplicate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-duplicate.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-duplicate-common.Plo
+ -rm -f ./$(DEPDIR)/ext-duplicate.Plo
+ -rm -f ./$(DEPDIR)/tst-duplicate.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-duplicate-common.Plo
+ -rm -f ./$(DEPDIR)/ext-duplicate.Plo
+ -rm -f ./$(DEPDIR)/tst-duplicate.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
new file mode 100644
index 0000000..5af7e04
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
@@ -0,0 +1,298 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "md5.h"
+#include "ioloop.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-code.h"
+#include "sieve-runtime.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_DUPLICATE_DEFAULT_PERIOD (12*60*60)
+#define EXT_DUPLICATE_DEFAULT_MAX_PERIOD (2*24*60*60)
+
+bool ext_duplicate_load(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_duplicate_config *config;
+ sieve_number_t default_period, max_period;
+
+ if (*context != NULL)
+ ext_duplicate_unload(ext);
+
+ if (!sieve_setting_get_duration_value(
+ svinst, "sieve_duplicate_default_period", &default_period))
+ default_period = EXT_DUPLICATE_DEFAULT_PERIOD;
+ if (!sieve_setting_get_duration_value(
+ svinst, "sieve_duplicate_max_period", &max_period)) {
+ max_period = EXT_DUPLICATE_DEFAULT_MAX_PERIOD;
+ }
+
+ config = i_new(struct ext_duplicate_config, 1);
+ config->default_period = default_period;
+ config->max_period = max_period;
+
+ *context = (void *) config;
+ return TRUE;
+}
+
+void ext_duplicate_unload(const struct sieve_extension *ext)
+{
+ struct ext_duplicate_config *config =
+ (struct ext_duplicate_config *)ext->context;
+
+ i_free(config);
+}
+
+/*
+ * Duplicate_mark action
+ */
+
+struct act_duplicate_mark_data {
+ const char *handle;
+ unsigned int period;
+ unsigned char hash[MD5_RESULTLEN];
+ bool last:1;
+};
+
+static void
+act_duplicate_mark_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep);
+static void
+act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
+ void *tr_context, int status);
+
+static const struct sieve_action_def act_duplicate_mark = {
+ .name = "duplicate_mark",
+ .print = act_duplicate_mark_print,
+ .finish = act_duplicate_mark_finish
+};
+
+static void
+act_duplicate_mark_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ struct act_duplicate_mark_data *data =
+ (struct act_duplicate_mark_data *)action->context;
+ const char *last = (data->last ? " last" : "");
+
+ if (data->handle != NULL) {
+ sieve_result_action_printf(
+ rpenv, "track%s duplicate with handle: %s",
+ last, str_sanitize(data->handle, 128));
+ } else {
+ sieve_result_action_printf(rpenv, "track%s duplicate", last);
+ }
+}
+
+static void
+act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED, int status)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_duplicate_mark_data *data =
+ (struct act_duplicate_mark_data *)aenv->action->context;
+
+ if (status != SIEVE_EXEC_OK) {
+ e_debug(aenv->event, "Not marking duplicate (status=%s)",
+ sieve_execution_exitcode_to_str(status));
+ return;
+ }
+
+ e_debug(aenv->event, "Marking duplicate");
+
+ /* Message was handled successfully, so track duplicate for this
+ * message.
+ */
+ eenv->exec_status->significant_action_executed = TRUE;
+ sieve_action_duplicate_mark(aenv, data->hash, sizeof(data->hash),
+ ioloop_time + data->period);
+}
+
+/*
+ * Duplicate checking
+ */
+
+struct ext_duplicate_handle {
+ const char *handle;
+ bool last:1;
+ bool duplicate:1;
+};
+
+struct ext_duplicate_hash {
+ unsigned char hash[MD5_RESULTLEN];
+ ARRAY(struct ext_duplicate_handle) handles;
+};
+
+struct ext_duplicate_context {
+ ARRAY(struct ext_duplicate_hash) hashes;
+};
+
+static void
+ext_duplicate_hash(string_t *handle, const char *value, size_t value_len,
+ bool last, unsigned char hash_r[])
+{
+ static const char *id = "sieve duplicate";
+ struct md5_context md5ctx;
+
+ md5_init(&md5ctx);
+ md5_update(&md5ctx, id, strlen(id));
+ if (last)
+ md5_update(&md5ctx, "0", 1);
+ else
+ md5_update(&md5ctx, "+", 1);
+ if (handle != NULL) {
+ md5_update(&md5ctx, "h-", 2);
+ md5_update(&md5ctx, str_c(handle), str_len(handle));
+ } else {
+ md5_update(&md5ctx, "default", 7);
+ }
+ md5_update(&md5ctx, value, value_len);
+ md5_final(&md5ctx, hash_r);
+}
+
+int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
+ const char *value, size_t value_len,
+ sieve_number_t period, bool last, bool *duplicate_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_duplicate_context *rctx;
+ bool duplicate = FALSE;
+ pool_t msg_pool = NULL, result_pool = NULL;
+ unsigned char hash[MD5_RESULTLEN];
+ struct ext_duplicate_hash *hash_record = NULL;
+ struct ext_duplicate_handle *handle_record = NULL;
+ struct act_duplicate_mark_data *act;
+ int ret;
+
+ *duplicate_r = FALSE;
+
+ if (!sieve_execute_duplicate_check_available(eenv)) {
+ sieve_runtime_warning(
+ renv, NULL, "duplicate test: "
+ "duplicate checking not available in this context");
+ return SIEVE_EXEC_OK;
+ }
+
+ if (value == NULL)
+ return SIEVE_EXEC_OK;
+
+ /* Create hash */
+ ext_duplicate_hash(handle, value, value_len, last, hash);
+
+ /* Get context; find out whether duplicate was checked earlier */
+ rctx = (struct ext_duplicate_context *)
+ sieve_message_context_extension_get(renv->msgctx, this_ext);
+
+ if (rctx == NULL) {
+ /* Create context */
+ msg_pool = sieve_message_context_pool(renv->msgctx);
+ rctx = p_new(msg_pool, struct ext_duplicate_context, 1);
+ sieve_message_context_extension_set(renv->msgctx, this_ext,
+ (void *)rctx);
+ } else if (array_is_created(&rctx->hashes)) {
+ struct ext_duplicate_hash *record;
+
+ array_foreach_modifiable(&rctx->hashes, record) {
+ if (memcmp(record->hash, hash, MD5_RESULTLEN) == 0) {
+ hash_record = record;
+ break;
+ }
+ }
+ }
+ if (hash_record != NULL) {
+ const struct ext_duplicate_handle *rhandle;
+ array_foreach(&hash_record->handles, rhandle) {
+ const char *handle_str =
+ (handle == NULL ? NULL : str_c(handle));
+ if (null_strcmp(rhandle->handle, handle_str) == 0 &&
+ rhandle->last == last)
+ return (rhandle->duplicate ?
+ SIEVE_DUPLICATE_CHECK_RESULT_EXISTS :
+ SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND);
+ }
+ }
+
+ result_pool = sieve_result_pool(renv->result);
+ act = p_new(result_pool, struct act_duplicate_mark_data, 1);
+ if (handle != NULL)
+ act->handle = p_strdup(result_pool, str_c(handle));
+ act->period = period;
+ memcpy(act->hash, hash, MD5_RESULTLEN);
+ act->last = last;
+
+ /* Check duplicate */
+ ret = sieve_execute_duplicate_check(eenv, hash, sizeof(hash),
+ &duplicate);
+ if (ret >= SIEVE_EXEC_OK && !duplicate && last) {
+ unsigned char no_last_hash[MD5_RESULTLEN];
+
+ /* Check for entry without :last */
+ ext_duplicate_hash(handle, value, value_len,
+ FALSE, no_last_hash);
+ ret = sieve_execute_duplicate_check(
+ eenv, no_last_hash, sizeof(no_last_hash),
+ &duplicate);
+ }
+ if (ret < SIEVE_EXEC_OK) {
+ sieve_runtime_critical(
+ renv, NULL, "failed to check for duplicate",
+ "failed to check for duplicate%s",
+ (ret == SIEVE_EXEC_TEMP_FAILURE ?
+ " (temporary failure)" : ""));
+ return ret;
+ }
+
+ /* We may only mark the message as duplicate when Sieve script executes
+ * successfully; therefore defer this operation until successful result
+ * execution.
+ */
+ if (!duplicate || last) {
+ if (sieve_result_add_action(renv, NULL, NULL,
+ &act_duplicate_mark,
+ NULL, (void *) act, 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Cache result */
+ if (msg_pool == NULL)
+ msg_pool = sieve_message_context_pool(renv->msgctx);
+ if (hash_record == NULL) {
+ if (!array_is_created(&rctx->hashes))
+ p_array_init(&rctx->hashes, msg_pool, 64);
+ hash_record = array_append_space(&rctx->hashes);
+ memcpy(hash_record->hash, hash, MD5_RESULTLEN);
+ p_array_init(&hash_record->handles, msg_pool, 64);
+ }
+
+ handle_record = array_append_space(&hash_record->handles);
+ if (handle != NULL)
+ handle_record->handle = p_strdup(msg_pool, str_c(handle));
+ handle_record->last = last;
+ handle_record->duplicate = duplicate;
+
+ *duplicate_r = duplicate;
+
+ return SIEVE_EXEC_OK;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
new file mode 100644
index 0000000..c802b08
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
@@ -0,0 +1,41 @@
+#ifndef EXT_DUPLICATE_COMMON_H
+#define EXT_DUPLICATE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension
+ */
+
+struct ext_duplicate_config {
+ unsigned int default_period;
+ unsigned int max_period;
+};
+
+bool ext_duplicate_load(const struct sieve_extension *ext, void **context);
+void ext_duplicate_unload(const struct sieve_extension *ext);
+
+extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def vnd_duplicate_extension;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def tst_duplicate;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def tst_duplicate_operation;
+
+/*
+ * Duplicate checking
+ */
+
+int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
+ const char *value, size_t value_len,
+ sieve_number_t period, bool last, bool *duplicate_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate.c b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate.c
new file mode 100644
index 0000000..54de5e5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate.c
@@ -0,0 +1,107 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension duplicate
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-duplicate
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+/* Extension vnd.dovecot.duplicate
+ * -------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-duplicate
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+
+#include "ext-duplicate-common.h"
+
+/*
+ * Extensions
+ */
+
+static bool ext_duplicate_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def duplicate_extension = {
+ .name = "duplicate",
+ .load = ext_duplicate_load,
+ .unload = ext_duplicate_unload,
+ .validator_load = ext_duplicate_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(tst_duplicate_operation)
+};
+
+const struct sieve_extension_def vnd_duplicate_extension = {
+ .name = "vnd.dovecot.duplicate",
+ .load = ext_duplicate_load,
+ .unload = ext_duplicate_unload,
+ .validator_load = ext_duplicate_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(tst_duplicate_operation)
+};
+
+/*
+ * Validation
+ */
+
+static bool ext_duplicate_validator_check_conflict
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required);
+
+const struct sieve_validator_extension
+duplicate_validator_extension = {
+ .ext = &vnd_duplicate_extension,
+ .check_conflict = ext_duplicate_validator_check_conflict
+};
+
+static bool ext_duplicate_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register validator extension to check for conflict between
+ vnd.dovecot.duplicate and duplicate extensions */
+ if ( sieve_extension_is(ext, vnd_duplicate_extension) ) {
+ sieve_validator_extension_register
+ (valdtr, ext, &duplicate_validator_extension, NULL);
+ }
+
+ /* Register duplicate test */
+ sieve_validator_register_command(valdtr, ext, &tst_duplicate);
+
+ return TRUE;
+}
+
+static bool ext_duplicate_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required ATTR_UNUSED)
+{
+ /* Check for conflict with duplicate extension */
+ if ( sieve_extension_name_is(ext_other, "duplicate") ) {
+ sieve_argument_validate_error(valdtr, require_arg,
+ "the (deprecated) vnd.dovecot.duplicate extension "
+ "cannot be used together with the duplicate extension");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c b/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c
new file mode 100644
index 0000000..d211e04
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c
@@ -0,0 +1,449 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-duplicate-common.h"
+
+/* Duplicate test
+ *
+ * Syntax:
+ * Usage: "duplicate" [":handle" <handle: string>]
+ * [":header" <header-name: string> /
+ * ":uniqueid" <value: string>]
+ * [":seconds" <timeout: number>] [":last"]
+ */
+
+static bool
+tst_duplicate_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+tst_duplicate_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def tst_duplicate = {
+ .identifier = "duplicate",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_duplicate_registered,
+ .generate = tst_duplicate_generate
+};
+
+/*
+ * Duplicate test tags
+ */
+
+static bool
+tst_duplicate_validate_number_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+tst_duplicate_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def duplicate_seconds_tag = {
+ .identifier = "seconds",
+ .validate = tst_duplicate_validate_number_tag
+};
+
+static const struct sieve_argument_def duplicate_header_tag = {
+ .identifier = "header",
+ .validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_uniqueid_tag = {
+ .identifier = "uniqueid",
+ .validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_value_tag = {
+ .identifier = "value", /* vnd.dovecot.duplicate (deprecated) */
+ .validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_handle_tag = {
+ .identifier = "handle",
+ .validate = tst_duplicate_validate_string_tag
+};
+
+static const struct sieve_argument_def duplicate_last_tag = {
+ .identifier = "last"
+};
+
+/* Codes for optional arguments */
+
+enum tst_duplicate_optional {
+ OPT_END,
+ OPT_SECONDS,
+ OPT_HEADER,
+ OPT_UNIQUEID,
+ OPT_LAST,
+ OPT_HANDLE
+};
+
+/*
+ * Duplicate operation
+ */
+
+static bool
+tst_duplicate_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_duplicate_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def tst_duplicate_operation = {
+ .mnemonic = "DUPLICATE",
+ .ext_def = &duplicate_extension,
+ .dump = tst_duplicate_operation_dump,
+ .execute = tst_duplicate_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tst_duplicate_validate_number_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *ext = sieve_argument_ext(*arg);
+ const struct ext_duplicate_config *config =
+ (const struct ext_duplicate_config *)ext->context;
+ struct sieve_ast_argument *tag = *arg;
+ sieve_number_t seconds;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :seconds number
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_NUMBER, FALSE))
+ return FALSE;
+
+ seconds = sieve_ast_argument_number(*arg);
+ /* Enforce :days <= max_period */
+ if (config->max_period > 0 && seconds > config->max_period) {
+ seconds = config->max_period;
+
+ sieve_argument_validate_warning(
+ valdtr, *arg,
+ "specified :seconds value '%llu' is over the maximum",
+ (unsigned long long)seconds);
+ }
+
+ sieve_ast_argument_number_set(*arg, seconds);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+static bool
+tst_duplicate_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *ext = cmd->ext;
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :header <header-name: string>
+ * :value <value: string>
+ * :handle <handle: string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+
+ if (!sieve_argument_is(tag, duplicate_handle_tag) && (bool)cmd->data) {
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "conflicting :header and %s arguments specified "
+ "for the duplicate test",
+ (sieve_extension_is(ext, duplicate_extension) ?
+ ":uniqueid" : ":value"));
+ return FALSE;
+ }
+
+ /* :header <header-name: string> */
+ if (sieve_argument_is(tag, duplicate_header_tag)) {
+ if (!sieve_command_verify_headers_argument(valdtr, *arg))
+ return FALSE;
+ cmd->data = (void *)TRUE;
+ /* :handle <handle: string> */
+ } else if (sieve_argument_is(tag, duplicate_handle_tag)) {
+ /* nothing to be done */
+ } else if (sieve_argument_is(tag, duplicate_uniqueid_tag)) {
+ i_assert(sieve_extension_is(ext, duplicate_extension));
+ cmd->data = (void *)TRUE;
+ /* :value <value: string> (vnd.dovecot.duplicate) */
+ } else if (sieve_argument_is(tag, duplicate_value_tag)) {
+ i_assert(sieve_extension_is(ext, vnd_duplicate_extension));
+ cmd->data = (void *)TRUE;
+ } else {
+ i_unreached();
+ }
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+tst_duplicate_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_seconds_tag, OPT_SECONDS);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_last_tag, OPT_LAST);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_header_tag, OPT_HEADER);
+ if (sieve_extension_is(ext, duplicate_extension)) {
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_uniqueid_tag,
+ OPT_UNIQUEID);
+ } else {
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_value_tag,
+ OPT_UNIQUEID);
+ }
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &duplicate_handle_tag, OPT_HANDLE);
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tst_duplicate_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_duplicate_operation);
+
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_duplicate_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *ext = denv->oprtn->ext;
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "DUPLICATE");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_SECONDS:
+ opok = sieve_opr_number_dump(denv, address, "seconds");
+ break;
+ case OPT_LAST:
+ sieve_code_dumpf(denv, "last");
+ break;
+ case OPT_HEADER:
+ opok = sieve_opr_string_dump(denv, address, "header");
+ break;
+ case OPT_UNIQUEID:
+ if (sieve_extension_is(ext, duplicate_extension)) {
+ opok = sieve_opr_string_dump(denv, address,
+ "uniqueid");
+ } else {
+ opok = sieve_opr_string_dump(denv, address,
+ "value");
+ }
+ break;
+ case OPT_HANDLE:
+ opok = sieve_opr_string_dump(denv, address, "handle");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_duplicate_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *ext = renv->oprtn->ext;
+ const struct ext_duplicate_config *config =
+ (const struct ext_duplicate_config *)ext->context;
+ struct mail *mail = eenv->msgdata->mail;
+ int opt_code = 0;
+ string_t *handle = NULL, *header = NULL, *uniqueid = NULL;
+ const char *val = NULL;
+ size_t val_len = 0;
+ sieve_number_t seconds = config->default_period;
+ bool last = FALSE, duplicate = FALSE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_SECONDS:
+ ret = sieve_opr_number_read(renv, address, "seconds",
+ &seconds);
+ break;
+ case OPT_LAST:
+ last = TRUE;
+ ret = SIEVE_EXEC_OK;
+ break;
+ case OPT_HEADER:
+ ret = sieve_opr_string_read(renv, address, "header",
+ &header);
+ break;
+ case OPT_UNIQUEID:
+ if (sieve_extension_is(ext, duplicate_extension)) {
+ ret = sieve_opr_string_read(renv, address,
+ "uniqueid",
+ &uniqueid);
+ } else {
+ ret = sieve_opr_string_read(renv, address,
+ "value", &uniqueid);
+ }
+ break;
+ case OPT_HANDLE:
+ ret = sieve_opr_string_read(renv, address,
+ "handle", &handle);
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0)
+ return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ /* Trace */
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "duplicate test");
+ sieve_runtime_trace_descend(renv);
+
+ /* Get value */
+ if (uniqueid != NULL) {
+ val = str_c(uniqueid);
+ val_len = str_len(uniqueid);
+ } else {
+ if (header == NULL) {
+ ret = mail_get_message_id(mail, &val);
+ if (ret < 0) {
+ return sieve_runtime_mail_error(
+ renv, mail, "duplicate test: "
+ "failed to read header field `message-id'");
+ }
+ } else {
+ ret = mail_get_first_header_utf8(mail, str_c(header),
+ &val);
+ if (ret < 0) {
+ return sieve_runtime_mail_error(
+ renv, mail, "duplicate test: "
+ "failed to read header field `%s'",
+ str_c(header));
+ }
+ }
+
+ if (ret > 0)
+ val_len = strlen(val);
+ }
+
+ /* Check duplicate */
+ if (val == NULL) {
+ duplicate = FALSE;
+ } else {
+ ret = ext_duplicate_check(renv, handle, val, val_len,
+ seconds, last, &duplicate);
+ if (ret < SIEVE_EXEC_OK)
+ return ret;
+ }
+
+ /* Trace */
+ if (duplicate) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "message is a duplicate");
+ } else {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "message is not a duplicate");
+ }
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, duplicate);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.am b/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.am
new file mode 100644
index 0000000..6824ec0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_ext_editheader.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-addheader.c \
+ cmd-deleteheader.c
+
+libsieve_ext_editheader_la_SOURCES = \
+ $(commands) \
+ ext-editheader.c \
+ ext-editheader-common.c
+
+noinst_HEADERS = \
+ ext-editheader-limits.h \
+ ext-editheader-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in b/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in
new file mode 100644
index 0000000..75d9571
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in
@@ -0,0 +1,701 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/editheader
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_editheader_la_LIBADD =
+am__objects_1 = cmd-addheader.lo cmd-deleteheader.lo
+am_libsieve_ext_editheader_la_OBJECTS = $(am__objects_1) \
+ ext-editheader.lo ext-editheader-common.lo
+libsieve_ext_editheader_la_OBJECTS = \
+ $(am_libsieve_ext_editheader_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-addheader.Plo \
+ ./$(DEPDIR)/cmd-deleteheader.Plo \
+ ./$(DEPDIR)/ext-editheader-common.Plo \
+ ./$(DEPDIR)/ext-editheader.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_editheader_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_editheader_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_editheader.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-addheader.c \
+ cmd-deleteheader.c
+
+libsieve_ext_editheader_la_SOURCES = \
+ $(commands) \
+ ext-editheader.c \
+ ext-editheader-common.c
+
+noinst_HEADERS = \
+ ext-editheader-limits.h \
+ ext-editheader-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/editheader/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/editheader/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_editheader.la: $(libsieve_ext_editheader_la_OBJECTS) $(libsieve_ext_editheader_la_DEPENDENCIES) $(EXTRA_libsieve_ext_editheader_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_editheader_la_OBJECTS) $(libsieve_ext_editheader_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-addheader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-deleteheader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-editheader-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-editheader.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-addheader.Plo
+ -rm -f ./$(DEPDIR)/cmd-deleteheader.Plo
+ -rm -f ./$(DEPDIR)/ext-editheader-common.Plo
+ -rm -f ./$(DEPDIR)/ext-editheader.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-addheader.Plo
+ -rm -f ./$(DEPDIR)/cmd-deleteheader.Plo
+ -rm -f ./$(DEPDIR)/ext-editheader-common.Plo
+ -rm -f ./$(DEPDIR)/ext-editheader.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/cmd-addheader.c b/pigeonhole/src/lib-sieve/plugins/editheader/cmd-addheader.c
new file mode 100644
index 0000000..f39cbb7
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/cmd-addheader.c
@@ -0,0 +1,337 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+#include "edit-mail.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Addheader command
+ *
+ * Syntax
+ * "addheader" [":last"] <field-name: string> <value: string>
+ */
+
+static bool cmd_addheader_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_addheader_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_addheader_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def addheader_command = {
+ .identifier = "addheader",
+ .type = SCT_COMMAND,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_addheader_registered,
+ .validate = cmd_addheader_validate,
+ .generate = cmd_addheader_generate
+};
+
+/*
+ * Addheader command tags
+ */
+
+/* Argument objects */
+
+static const struct sieve_argument_def addheader_last_tag = {
+ .identifier = "last"
+};
+
+/* Codes for optional arguments */
+
+enum cmd_addheader_optional {
+ OPT_END,
+ OPT_LAST
+};
+
+/*
+ * Addheader operation
+ */
+
+static bool cmd_addheader_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_addheader_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def addheader_operation = {
+ .mnemonic = "ADDHEADER",
+ .ext_def = &editheader_extension,
+ .code = EXT_EDITHEADER_OPERATION_ADDHEADER,
+ .dump = cmd_addheader_operation_dump,
+ .execute = cmd_addheader_operation_execute
+};
+
+/*
+ * Utility
+ */
+
+static bool _str_contains_nul(const string_t *str)
+{
+ const unsigned char *p, *pend;
+
+ p = str_data(str);
+ pend = p + str_len(str);
+ while (p < pend) {
+ if (*p == '\0')
+ return TRUE;
+ p++;
+ }
+ return FALSE;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_addheader_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check field-name syntax */
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "field-name", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ if ( sieve_argument_is_string_literal(arg) ) {
+ string_t *fname = sieve_ast_argument_str(arg);
+
+ if ( !rfc2822_header_field_name_verify(str_c(fname), str_len(fname)) ) {
+ sieve_argument_validate_error
+ (valdtr, arg, "addheader command: specified field name `%s' is invalid",
+ str_sanitize(str_c(fname), 80));
+ return FALSE;
+ }
+
+ if ( !ext_editheader_header_allow_add
+ (cmd->ext, str_c(fname)) ) {
+ sieve_argument_validate_warning
+ (valdtr, arg, "addheader command: "
+ "adding specified header field `%s' is forbidden; "
+ "modification will be denied",
+ str_sanitize(str_c(fname), 80));
+ }
+ }
+
+ /* Check value syntax */
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ if ( sieve_argument_is_string_literal(arg) ) {
+ string_t *fvalue = sieve_ast_argument_str(arg);
+
+ if ( _str_contains_nul(fvalue) ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "addheader command: specified value `%s' is invalid "
+ "(contains NUL character)", str_sanitize(str_c(fvalue), 80));
+ return FALSE;
+ }
+
+ if ( !rfc2822_header_field_body_verify
+ (str_c(fvalue), str_len(fvalue), TRUE, TRUE) ) {
+ sieve_argument_validate_warning(valdtr, arg,
+ "addheader command: specified value `%s' is invalid",
+ str_sanitize(str_c(fvalue), 80));
+ }
+
+ if ( ext_editheader_header_too_large(cmd->ext, str_len(fvalue)) ) {
+ sieve_argument_validate_error(valdtr, arg, "addheader command: "
+ "specified header value `%s' is too large (%zu bytes)",
+ str_sanitize(str_c(fvalue), 80), str_len(fvalue));
+ return SIEVE_EXEC_FAILURE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_addheader_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &addheader_last_tag, OPT_LAST);
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_addheader_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &addheader_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_addheader_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "addheader");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ if ( opt_code == OPT_LAST ) {
+ sieve_code_dumpf(denv, "last");
+ } else {
+ return FALSE;
+ }
+ }
+
+ return
+ sieve_opr_string_dump(denv, address, "field-name") &&
+ sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_addheader_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ string_t *field_name;
+ string_t *value;
+ struct edit_mail *edmail;
+ bool last = FALSE;
+ int opt_code = 0;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_LAST:
+ last = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read message */
+
+ if ( (ret=sieve_opr_string_read
+ (renv, address, "field-name", &field_name)) <= 0 )
+ return ret;
+
+ if ( (ret=sieve_opr_string_read
+ (renv, address, "value", &value)) <= 0 )
+ return ret;
+
+ /*
+ * Verify arguments
+ */
+
+ if ( !rfc2822_header_field_name_verify
+ (str_c(field_name), str_len(field_name)) ) {
+ sieve_runtime_error(renv, NULL, "addheader action: "
+ "specified field name `%s' is invalid",
+ str_sanitize(str_c(field_name), 80));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if ( !ext_editheader_header_allow_add
+ (this_ext, str_c(field_name)) ) {
+ sieve_runtime_warning(renv, NULL, "addheader action: "
+ "adding specified header field `%s' is forbidden; "
+ "modification denied", str_sanitize(str_c(field_name), 80));
+ return SIEVE_EXEC_OK;
+ }
+
+ if ( _str_contains_nul(value) ) {
+ sieve_runtime_error(renv, NULL, "addheader action: "
+ "specified value `%s' is invalid (contains NUL character)",
+ str_sanitize(str_c(value), 80));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if ( ext_editheader_header_too_large(this_ext, str_len(value)) ) {
+ sieve_runtime_error(renv, NULL, "addheader action: "
+ "specified header value `%s' is too large (%zu bytes)",
+ str_sanitize(str_c(value), 80), str_len(value));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "addheader \"%s: %s\"",
+ str_sanitize(str_c(field_name), 80), str_sanitize(str_c(value), 80));
+
+ edmail = sieve_message_edit(renv->msgctx);
+ edit_mail_header_add(edmail,
+ rfc2822_header_field_name_sanitize(str_c(field_name)),
+ str_c(value), last);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.c b/pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.c
new file mode 100644
index 0000000..a6964b7
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.c
@@ -0,0 +1,551 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+#include "edit-mail.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Deleteheader command
+ *
+ * Syntax:
+ * deleteheader [":index" <fieldno: number> [":last"]]
+ * [COMPARATOR] [MATCH-TYPE]
+ * <field-name: string> [<value-patterns: string-list>]
+ */
+
+static bool cmd_deleteheader_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_deleteheader_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_deleteheader_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def deleteheader_command = {
+ .identifier = "deleteheader",
+ .type = SCT_COMMAND,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_deleteheader_registered,
+ .validate = cmd_deleteheader_validate,
+ .generate = cmd_deleteheader_generate
+};
+
+/*
+ * Deleteheader command tags
+ */
+
+/* Forward declarations */
+
+static bool cmd_deleteheader_validate_index_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool cmd_deleteheader_validate_last_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def deleteheader_index_tag = {
+ .identifier = "index",
+ .validate = cmd_deleteheader_validate_index_tag
+};
+
+static const struct sieve_argument_def deleteheader_last_tag = {
+ .identifier = "last",
+ .validate = cmd_deleteheader_validate_last_tag
+};
+
+/* Codes for optional arguments */
+
+enum cmd_deleteheader_optional {
+ OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+ OPT_LAST
+};
+
+/*
+ * Deleteheader operation
+ */
+
+static bool cmd_deleteheader_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_deleteheader_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def deleteheader_operation = {
+ .mnemonic = "DELETEHEADER",
+ .ext_def = &editheader_extension,
+ .code = EXT_EDITHEADER_OPERATION_DELETEHEADER,
+ .dump = cmd_deleteheader_operation_dump,
+ .execute = cmd_deleteheader_operation_execute
+};
+
+/*
+ * Command registration
+ */
+
+static bool cmd_deleteheader_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &deleteheader_index_tag, OPT_INDEX);
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &deleteheader_last_tag, OPT_LAST);
+
+ return TRUE;
+}
+
+/*
+ * Command validation context
+ */
+
+struct cmd_deleteheader_context_data {
+ struct sieve_ast_argument *arg_index;
+ struct sieve_ast_argument *arg_last;
+};
+
+/*
+ * Tag validation
+ */
+
+static struct cmd_deleteheader_context_data *
+cmd_deleteheader_get_context
+(struct sieve_command *cmd)
+{
+ struct cmd_deleteheader_context_data *ctx_data =
+ (struct cmd_deleteheader_context_data *)cmd->data;
+
+ if ( ctx_data != NULL ) return ctx_data;
+
+ ctx_data = p_new
+ (sieve_command_pool(cmd), struct cmd_deleteheader_context_data, 1);
+ cmd->data = (void *)ctx_data;
+
+ return ctx_data;
+}
+
+static bool cmd_deleteheader_validate_index_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_deleteheader_context_data *ctx_data;
+ sieve_number_t index;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :index number
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+ return FALSE;
+ }
+
+ index = sieve_ast_argument_number(*arg);
+ if ( index > INT_MAX ) {
+ sieve_argument_validate_warning(valdtr, *arg,
+ "the :%s tag for the %s %s has a parameter value '%llu' "
+ "exceeding the maximum (%d)",
+ sieve_argument_identifier(tag), sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd), (unsigned long long) index,
+ INT_MAX);
+ return FALSE;
+ }
+
+ ctx_data = cmd_deleteheader_get_context(cmd);
+ ctx_data->arg_index = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool cmd_deleteheader_validate_last_tag
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_deleteheader_context_data *ctx_data;
+
+ ctx_data = cmd_deleteheader_get_context(cmd);
+ ctx_data->arg_last = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool cmd_deleteheader_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct cmd_deleteheader_context_data *ctx_data =
+ (struct cmd_deleteheader_context_data *)cmd->data;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+ if ( ctx_data != NULL ) {
+ if ( ctx_data->arg_last != NULL && ctx_data->arg_index == NULL ) {
+ sieve_argument_validate_error(valdtr, ctx_data->arg_last,
+ "the :last tag for the %s %s cannot be specified "
+ "without the :index tag",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+ }
+ }
+
+ /* Field name argument */
+
+ if ( arg == NULL ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the %s %s expects at least one positional argument, but none was found",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "field name", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ if ( sieve_argument_is_string_literal(arg) ) {
+ string_t *fname = sieve_ast_argument_str(arg);
+
+ if ( !rfc2822_header_field_name_verify(str_c(fname), str_len(fname)) ) {
+ sieve_argument_validate_error(valdtr, arg, "deleteheader command:"
+ "specified field name `%s' is invalid",
+ str_sanitize(str_c(fname), 80));
+ return FALSE;
+ }
+
+ if ( !ext_editheader_header_allow_delete
+ (cmd->ext, str_c(fname)) ) {
+ sieve_argument_validate_warning
+ (valdtr, arg, "deleteheader command: "
+ "deleting specified header field `%s' is forbidden; "
+ "modification will be denied",
+ str_sanitize(str_c(fname), 80));
+ }
+ }
+
+ /* Value patterns argument */
+
+ arg = sieve_ast_argument_next(arg);
+ if ( arg == NULL ) {
+ /* There is none; let's not generate code for useless match arguments */
+ sieve_match_type_arguments_remove(valdtr, cmd);
+
+ return TRUE;
+ }
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value patterns", 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the value patterns to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, cmd, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_deleteheader_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &deleteheader_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ /* Emit a placeholder when the value-patterns argument is missing */
+ if ( sieve_ast_argument_next(cmd->first_positional) == NULL )
+ sieve_opr_omitted_emit(cgenv->sblock);
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_deleteheader_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "DELETEHEADER");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_INDEX:
+ if ( !sieve_opr_number_dump(denv, address, "index") )
+ return FALSE;
+ break;
+ case OPT_LAST:
+ sieve_code_dumpf(denv, "last");
+ break;
+ default:
+ return FALSE;
+ }
+ };
+
+ if ( !sieve_opr_string_dump(denv, address, "field name") )
+ return FALSE;
+
+ return sieve_opr_stringlist_dump_ex(denv, address, "value patterns", "");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_deleteheader_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ int opt_code = 0;
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ string_t *field_name;
+ struct sieve_stringlist *vpattern_list = NULL;
+ struct edit_mail *edmail;
+ sieve_number_t index_offset = 0;
+ bool index_last = FALSE;
+ bool trace = FALSE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_INDEX:
+ if ( (ret=sieve_opr_number_read(renv, address, "index", &index_offset))
+ <= 0 )
+ return ret;
+
+ if ( index_offset > INT_MAX ) {
+ sieve_runtime_trace_error(renv, "index is > %d", INT_MAX);
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ break;
+ case OPT_LAST:
+ index_last = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read field-name */
+ if ( (ret=sieve_opr_string_read(renv, address, "field-name", &field_name))
+ <= 0 )
+ return ret;
+
+ /* Read value-patterns */
+ if ( (ret=sieve_opr_stringlist_read_ex
+ (renv, address, "value-patterns", TRUE, &vpattern_list)) <= 0 )
+ return ret;
+
+ /*
+ * Verify arguments
+ */
+
+ if ( !rfc2822_header_field_name_verify
+ (str_c(field_name), str_len(field_name)) ) {
+ sieve_runtime_error(renv, NULL, "deleteheader action: "
+ "specified field name `%s' is invalid",
+ str_sanitize(str_c(field_name), 80));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if ( !ext_editheader_header_allow_delete
+ (this_ext, str_c(field_name)) ) {
+ sieve_runtime_warning(renv, NULL, "deleteheader action: "
+ "deleting specified header field `%s' is forbidden; "
+ "modification denied",
+ str_sanitize(str_c(field_name), 80));
+ return SIEVE_EXEC_OK;
+ }
+
+ /*
+ * Execute command
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "deleteheader command");
+
+ /* Start editing the mail */
+ edmail = sieve_message_edit(renv->msgctx);
+
+ trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS);
+
+ /* Either do string matching or just kill all/indexed notify action(s) */
+ if ( vpattern_list != NULL ) {
+ struct edit_mail_header_iter *edhiter;
+ struct sieve_match_context *mctx;
+
+ if ( trace ) {
+ sieve_runtime_trace_descend(renv);
+ if ( index_offset != 0 ) {
+ sieve_runtime_trace(renv, 0,
+ "deleting matching occurrences of header `%s' at index %llu%s",
+ str_c(field_name), (unsigned long long)index_offset,
+ ( index_last ? " from last": ""));
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "deleting matching occurrences of header `%s'", str_c(field_name));
+ }
+ }
+
+ /* Iterate through all headers and delete those that match */
+ if ( (ret=edit_mail_headers_iterate_init
+ (edmail, str_c(field_name), index_last, &edhiter)) > 0 )
+ {
+ int mret = 0;
+ sieve_number_t pos = 0;
+
+ /* Initialize match */
+ mctx = sieve_match_begin(renv, &mcht, &cmp);
+
+ /* Match */
+ for (;;) {
+ pos++;
+
+ /* Check index if any */
+ if ( index_offset == 0 || pos == index_offset ) {
+ const char *value;
+ int match;
+
+ /* Match value against all value patterns */
+ edit_mail_headers_iterate_get(edhiter, &value);
+ if ( (match=sieve_match_value
+ (mctx, value, strlen(value), vpattern_list)) < 0 )
+ break;
+
+ if ( match > 0 ) {
+ /* Remove it and iterate to next */
+ sieve_runtime_trace(renv, 0, "deleting header with value `%s'",
+ value);
+
+ if ( !edit_mail_headers_iterate_remove(edhiter) ) break;
+ continue;
+ }
+ }
+
+ if ( !edit_mail_headers_iterate_next(edhiter) )
+ break;
+ }
+
+ /* Finish match */
+ mret = sieve_match_end(&mctx, &ret);
+
+ edit_mail_headers_iterate_deinit(&edhiter);
+
+ if ( mret < 0 )
+ return ret;
+ }
+
+ if ( ret == 0 ) {
+ sieve_runtime_trace(renv, 0, "header `%s' not found", str_c(field_name));
+ } else if ( ret < 0 ) {
+ sieve_runtime_warning(renv, NULL, "deleteheader action: "
+ "failed to delete occurrences of header `%s' (this should not happen!)",
+ str_c(field_name));
+ }
+
+ } else {
+ int index = ( index_last ? -((int)index_offset) : ((int)index_offset) );
+
+ if ( trace ) {
+ sieve_runtime_trace_descend(renv);
+ if ( index_offset != 0 ) {
+ sieve_runtime_trace(renv, 0, "deleting header `%s' at index %llu%s",
+ str_c(field_name), (unsigned long long)index_offset,
+ ( index_last ? " from last": ""));
+ } else {
+ sieve_runtime_trace(renv, 0, "deleting header `%s'", str_c(field_name));
+ }
+ }
+
+ /* Delete all occurrences of header */
+ ret = edit_mail_header_delete(edmail, str_c(field_name), index);
+
+ if ( ret < 0 ) {
+ sieve_runtime_warning(renv, NULL, "deleteheader action: "
+ "failed to delete occurrences of header `%s' (this should not happen!)",
+ str_c(field_name));
+ } else if ( trace ) {
+ sieve_runtime_trace(renv, 0, "deleted %d occurrences of header `%s'",
+ ret, str_c(field_name));
+ }
+
+ }
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.c b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.c
new file mode 100644
index 0000000..89fb180
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.c
@@ -0,0 +1,211 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "ext-editheader-limits.h"
+#include "ext-editheader-common.h"
+
+/*
+ * Extension configuration
+ */
+
+struct ext_editheader_header {
+ const char *name;
+
+ bool forbid_add:1;
+ bool forbid_delete:1;
+};
+
+struct ext_editheader_config {
+ pool_t pool;
+
+ ARRAY(struct ext_editheader_header) headers;
+
+ size_t max_header_size;
+};
+
+static struct ext_editheader_header *
+ext_editheader_config_header_find(struct ext_editheader_config *ext_config,
+ const char *hname)
+{
+ struct ext_editheader_header *headers;
+ unsigned int count, i;
+
+ headers = array_get_modifiable(&ext_config->headers, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(hname, headers[i].name) == 0)
+ return &headers[i];
+ }
+ return NULL;
+}
+
+static void
+ext_editheader_config_headers(struct sieve_instance *svinst,
+ struct ext_editheader_config *ext_config,
+ const char *setting, bool forbid_add,
+ bool forbid_delete)
+{
+ const char *setval;
+
+ setval = sieve_setting_get(svinst, setting);
+ if (setval != NULL) {
+ const char **headers = t_strsplit_spaces(setval, " \t");
+
+ while (*headers != NULL) {
+ struct ext_editheader_header *header;
+
+ if (!rfc2822_header_field_name_verify(
+ *headers, strlen(*headers))) {
+ e_warning(svinst->event, "editheader: "
+ "setting %s contains invalid header field name "
+ "`%s' (ignored)",
+ setting, *headers);
+ headers++;
+ continue;
+ }
+
+ header = ext_editheader_config_header_find(
+ ext_config, *headers);
+ if (header == NULL) {
+ header = array_append_space(
+ &ext_config->headers);
+ header->name = p_strdup(ext_config->pool,
+ *headers);
+ }
+
+ if (forbid_add)
+ header->forbid_add = TRUE;
+ if (forbid_delete)
+ header->forbid_delete = TRUE;
+
+ headers++;
+ }
+ }
+}
+
+bool ext_editheader_load(const struct sieve_extension *ext, void **context)
+{
+ struct ext_editheader_config *ext_config;
+ struct sieve_instance *svinst = ext->svinst;
+ size_t max_header_size;
+ pool_t pool;
+
+ if (*context != NULL) {
+ ext_editheader_unload(ext);
+ *context = NULL;
+ }
+
+ T_BEGIN {
+ pool = pool_alloconly_create("editheader_config", 1024);
+ ext_config = p_new(pool, struct ext_editheader_config, 1);
+ ext_config->pool = pool;
+ ext_config->max_header_size =
+ EXT_EDITHEADER_DEFAULT_MAX_HEADER_SIZE;
+
+ p_array_init(&ext_config->headers, pool, 16);
+
+ ext_editheader_config_headers(
+ svinst, ext_config,
+ "sieve_editheader_protected", TRUE, TRUE);
+ ext_editheader_config_headers(
+ svinst, ext_config,
+ "sieve_editheader_forbid_add", TRUE, FALSE);
+ ext_editheader_config_headers(
+ svinst, ext_config,
+ "sieve_editheader_forbid_delete", FALSE, TRUE);
+
+ if (sieve_setting_get_size_value(
+ svinst, "sieve_editheader_max_header_size",
+ &max_header_size)) {
+ if (max_header_size < EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE) {
+ e_warning(svinst->event, "editheader: "
+ "value of sieve_editheader_max_header_size setting "
+ "(=%zu) is less than the minimum (=%zu) "
+ "(ignored)", max_header_size,
+ (size_t)EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE);
+ } else {
+ ext_config->max_header_size = max_header_size;
+ }
+ }
+ } T_END;
+
+ *context = (void *)ext_config;
+ return TRUE;
+}
+
+void ext_editheader_unload(const struct sieve_extension *ext)
+{
+ struct ext_editheader_config *ext_config =
+ (struct ext_editheader_config *)ext->context;
+
+ if (ext_config != NULL)
+ pool_unref(&ext_config->pool);
+}
+
+/*
+ * Protected headers
+ */
+
+bool ext_editheader_header_allow_add(const struct sieve_extension *ext,
+ const char *hname)
+{
+ struct ext_editheader_config *ext_config =
+ (struct ext_editheader_config *)ext->context;
+ const struct ext_editheader_header *header;
+
+ if (strcasecmp(hname, "subject") == 0)
+ return TRUE;
+ if (strcasecmp(hname, "x-sieve-redirected-from") == 0)
+ return FALSE;
+
+ header = ext_editheader_config_header_find(ext_config, hname);
+ if (header == NULL)
+ return TRUE;
+
+ return !header->forbid_add;
+}
+
+bool ext_editheader_header_allow_delete(const struct sieve_extension *ext,
+ const char *hname)
+{
+ struct ext_editheader_config *ext_config =
+ (struct ext_editheader_config *)ext->context;
+ const struct ext_editheader_header *header;
+
+ if (strcasecmp(hname, "received") == 0 ||
+ strcasecmp(hname, "auto-submitted") == 0)
+ return FALSE;
+ if (strcasecmp(hname, "x-sieve-redirected-from") == 0)
+ return FALSE;
+ if (strcasecmp(hname, "subject") == 0)
+ return TRUE;
+
+ header = ext_editheader_config_header_find(ext_config, hname);
+ if (header == NULL)
+ return TRUE;
+
+ return !header->forbid_delete;
+}
+
+/*
+ * Limits
+ */
+
+bool ext_editheader_header_too_large(const struct sieve_extension *ext,
+ size_t size)
+{
+ struct ext_editheader_config *ext_config =
+ (struct ext_editheader_config *)ext->context;
+
+ return size > ext_config->max_header_size;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.h b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.h
new file mode 100644
index 0000000..8a6cc36
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-common.h
@@ -0,0 +1,48 @@
+#ifndef EXT_EDITHEADER_COMMON_H
+#define EXT_EDITHEADER_COMMON_H
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def addheader_command;
+extern const struct sieve_command_def deleteheader_command;
+
+/*
+ * Operations
+ */
+
+enum ext_imap4flags_opcode {
+ EXT_EDITHEADER_OPERATION_ADDHEADER,
+ EXT_EDITHEADER_OPERATION_DELETEHEADER,
+};
+
+extern const struct sieve_operation_def addheader_operation;
+extern const struct sieve_operation_def deleteheader_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def editheader_extension;
+
+bool ext_editheader_load(const struct sieve_extension *ext, void **context);
+void ext_editheader_unload(const struct sieve_extension *ext);
+
+/*
+ * Protected headers
+ */
+
+bool ext_editheader_header_allow_add(const struct sieve_extension *ext,
+ const char *hname);
+bool ext_editheader_header_allow_delete(const struct sieve_extension *ext,
+ const char *hname);
+
+/*
+ * Limits
+ */
+
+bool ext_editheader_header_too_large(const struct sieve_extension *ext,
+ size_t size);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-limits.h b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-limits.h
new file mode 100644
index 0000000..a8e834b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader-limits.h
@@ -0,0 +1,7 @@
+#ifndef EXT_EDITHEADER_LIMITS_H
+#define EXT_EDITHEADER_LIMITS_H
+
+#define EXT_EDITHEADER_MINIMUM_MAX_HEADER_SIZE 1024
+#define EXT_EDITHEADER_DEFAULT_MAX_HEADER_SIZE 2048
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader.c b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader.c
new file mode 100644
index 0000000..dc8eb12
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/editheader/ext-editheader.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension debug
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5293
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-editheader-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *editheader_operations[] = {
+ &addheader_operation,
+ &deleteheader_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_editheader_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def editheader_extension = {
+ .name = "editheader",
+ .load = ext_editheader_load,
+ .unload = ext_editheader_unload,
+ .validator_load = ext_editheader_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(editheader_operations)
+};
+
+static bool ext_editheader_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ /* Register new commands */
+ sieve_validator_register_command(validator, ext, &addheader_command);
+ sieve_validator_register_command(validator, ext, &deleteheader_command);
+
+ return TRUE;
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.am b/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.am
new file mode 100644
index 0000000..4b65630
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.am
@@ -0,0 +1,44 @@
+SUBDIRS = mailto
+
+noinst_LTLIBRARIES = libsieve_ext_enotify.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-notify.c
+
+tests = \
+ tst-valid-notify-method.c \
+ tst-notify-method-capability.c
+
+var_modifiers = \
+ vmodf-encodeurl.c
+
+notify_methods = \
+ ./mailto/libsieve_ext_enotify_mailto.la
+
+libsieve_ext_enotify_la_DEPENDENCIES = \
+ $(notify_methods)
+libsieve_ext_enotify_la_LIBADD = \
+ $(notify_methods)
+
+libsieve_ext_enotify_la_SOURCES = \
+ ext-enotify.c \
+ ext-enotify-common.c \
+ $(commands) \
+ $(tests) \
+ $(var_modifiers)
+
+public_headers = \
+ sieve-ext-enotify.h
+
+headers = \
+ ext-enotify-limits.h \
+ ext-enotify-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in b/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in
new file mode 100644
index 0000000..fea2423
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in
@@ -0,0 +1,904 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/enotify
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+am__objects_1 = cmd-notify.lo
+am__objects_2 = tst-valid-notify-method.lo \
+ tst-notify-method-capability.lo
+am__objects_3 = vmodf-encodeurl.lo
+am_libsieve_ext_enotify_la_OBJECTS = ext-enotify.lo \
+ ext-enotify-common.lo $(am__objects_1) $(am__objects_2) \
+ $(am__objects_3)
+libsieve_ext_enotify_la_OBJECTS = \
+ $(am_libsieve_ext_enotify_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-notify.Plo \
+ ./$(DEPDIR)/ext-enotify-common.Plo ./$(DEPDIR)/ext-enotify.Plo \
+ ./$(DEPDIR)/tst-notify-method-capability.Plo \
+ ./$(DEPDIR)/tst-valid-notify-method.Plo \
+ ./$(DEPDIR)/vmodf-encodeurl.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_enotify_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_enotify_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = mailto
+noinst_LTLIBRARIES = libsieve_ext_enotify.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-notify.c
+
+tests = \
+ tst-valid-notify-method.c \
+ tst-notify-method-capability.c
+
+var_modifiers = \
+ vmodf-encodeurl.c
+
+notify_methods = \
+ ./mailto/libsieve_ext_enotify_mailto.la
+
+libsieve_ext_enotify_la_DEPENDENCIES = \
+ $(notify_methods)
+
+libsieve_ext_enotify_la_LIBADD = \
+ $(notify_methods)
+
+libsieve_ext_enotify_la_SOURCES = \
+ ext-enotify.c \
+ ext-enotify-common.c \
+ $(commands) \
+ $(tests) \
+ $(var_modifiers)
+
+public_headers = \
+ sieve-ext-enotify.h
+
+headers = \
+ ext-enotify-limits.h \
+ ext-enotify-common.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/enotify/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/enotify/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_enotify.la: $(libsieve_ext_enotify_la_OBJECTS) $(libsieve_ext_enotify_la_DEPENDENCIES) $(EXTRA_libsieve_ext_enotify_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_enotify_la_OBJECTS) $(libsieve_ext_enotify_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-notify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-enotify-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-enotify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-notify-method-capability.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-valid-notify-method.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vmodf-encodeurl.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/cmd-notify.Plo
+ -rm -f ./$(DEPDIR)/ext-enotify-common.Plo
+ -rm -f ./$(DEPDIR)/ext-enotify.Plo
+ -rm -f ./$(DEPDIR)/tst-notify-method-capability.Plo
+ -rm -f ./$(DEPDIR)/tst-valid-notify-method.Plo
+ -rm -f ./$(DEPDIR)/vmodf-encodeurl.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/cmd-notify.Plo
+ -rm -f ./$(DEPDIR)/ext-enotify-common.Plo
+ -rm -f ./$(DEPDIR)/ext-enotify.Plo
+ -rm -f ./$(DEPDIR)/tst-notify-method-capability.Plo
+ -rm -f ./$(DEPDIR)/tst-valid-notify-method.Plo
+ -rm -f ./$(DEPDIR)/vmodf-encodeurl.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pkginc_libHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/cmd-notify.c b/pigeonhole/src/lib-sieve/plugins/enotify/cmd-notify.c
new file mode 100644
index 0000000..4fe121a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/cmd-notify.c
@@ -0,0 +1,621 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def notify_importance_tag;
+static const struct sieve_argument_def notify_from_tag;
+static const struct sieve_argument_def notify_options_tag;
+static const struct sieve_argument_def notify_message_tag;
+
+/*
+ * Notify command
+ *
+ * Syntax:
+ * notify [":from" string]
+ * [":importance" <"1" / "2" / "3">]
+ * [":options" string-list]
+ * [":message" string]
+ * <method: string>
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_notify_pre_validate(struct sieve_validator *validator,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def notify_command = {
+ .identifier = "notify",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_notify_registered,
+ .pre_validate = cmd_notify_pre_validate,
+ .validate = cmd_notify_validate,
+ .generate = cmd_notify_generate,
+};
+
+/*
+ * Notify command tags
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate_importance_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def notify_from_tag = {
+ .identifier = "from",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_options_tag = {
+ .identifier = "options",
+ .validate = cmd_notify_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def notify_message_tag = {
+ .identifier = "message",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_importance_tag = {
+ .identifier = "importance",
+ .validate = cmd_notify_validate_importance_tag
+};
+
+/*
+ * Notify operation
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def notify_operation = {
+ .mnemonic = "NOTIFY",
+ .ext_def = &enotify_extension,
+ .code = EXT_ENOTIFY_OPERATION_NOTIFY,
+ .dump = cmd_notify_operation_dump,
+ .execute = cmd_notify_operation_execute
+};
+
+/*
+ * Notify action
+ */
+
+/* Forward declarations */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep);
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+
+/* Action object */
+
+const struct sieve_action_def act_notify = {
+ .name = "notify",
+ .check_duplicate =act_notify_check_duplicate,
+ .print = act_notify_print,
+ .commit = act_notify_commit,
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_notify_context_data {
+ struct sieve_ast_argument *from;
+ struct sieve_ast_argument *message;
+ struct sieve_ast_argument *options;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :from <string>
+ * :message <string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is(tag, notify_from_tag)) {
+ ctx_data->from = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ } else if (sieve_argument_is(tag, notify_message_tag)) {
+ ctx_data->message = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ }
+ return TRUE;
+}
+
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :options string-list
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING_LIST, FALSE))
+ return FALSE;
+
+ /* Assign context */
+ ctx_data->options = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool
+cmd_notify_validate_importance_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ const struct sieve_ast_argument *tag = *arg;
+ const char *impstr;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :importance <"1" / "2" / "3">
+ */
+ if (sieve_ast_argument_type(*arg) != SAAT_STRING) {
+ /* Not a string */
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "the :importance tag for the notify command requires a string parameter, "
+ "but %s was found", sieve_ast_argument_name(*arg));
+ return FALSE;
+ }
+
+ impstr = sieve_ast_argument_strc(*arg);
+ if (impstr[0] < '1' || impstr[0] > '3' || impstr[1] != '\0') {
+ /* Invalid importance */
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "invalid :importance value for notify command: %s",
+ impstr);
+ return FALSE;
+ }
+
+ sieve_ast_argument_number_substitute(*arg, impstr[0] - '0');
+ (*arg)->argument = sieve_argument_create((*arg)->ast, &number_argument,
+ tag->argument->ext,
+ tag->argument->id_code);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_importance_tag,
+ CMD_NOTIFY_OPT_IMPORTANCE);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_from_tag, CMD_NOTIFY_OPT_FROM);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_options_tag,
+ CMD_NOTIFY_OPT_OPTIONS);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_message_tag,
+ CMD_NOTIFY_OPT_MESSAGE);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_notify_pre_validate(struct sieve_validator *validator ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct cmd_notify_context_data *ctx_data;
+
+ /* Assign context */
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_notify_context_data, 1);
+ cmd->data = ctx_data;
+
+ return TRUE;
+}
+
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "method", 1,
+ SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ return ext_enotify_compile_check_arguments(
+ valdtr, cmd, arg, ctx_data->message, ctx_data->from,
+ ctx_data->options);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &notify_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "NOTIFY");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case CMD_NOTIFY_OPT_IMPORTANCE:
+ opok = sieve_opr_number_dump(denv, address,
+ "importance");
+ break;
+ case CMD_NOTIFY_OPT_FROM:
+ opok = sieve_opr_string_dump(denv, address, "from");
+ break;
+ case CMD_NOTIFY_OPT_OPTIONS:
+ opok = sieve_opr_stringlist_dump(denv, address,
+ "options");
+ break;
+ case CMD_NOTIFY_OPT_MESSAGE:
+ opok = sieve_opr_string_dump(denv, address, "message");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ /* Dump method operand */
+ return sieve_opr_string_dump(denv, address, "method");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct sieve_side_effects_list *slist = NULL;
+ struct sieve_enotify_action *act;
+ void *method_context;
+ pool_t pool;
+ int opt_code = 0;
+ sieve_number_t importance = 2;
+ struct sieve_stringlist *options = NULL;
+ const struct sieve_enotify_method *method;
+ string_t *method_uri, *message = NULL, *from = NULL;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0) break;
+
+ switch (opt_code) {
+ case CMD_NOTIFY_OPT_IMPORTANCE:
+ ret = sieve_opr_number_read(renv, address, "importance",
+ &importance);
+ break;
+ case CMD_NOTIFY_OPT_FROM:
+ ret = sieve_opr_string_read(renv, address, "from",
+ &from);
+ break;
+ case CMD_NOTIFY_OPT_MESSAGE:
+ ret = sieve_opr_string_read(renv, address, "message",
+ &message);
+ break;
+ case CMD_NOTIFY_OPT_OPTIONS:
+ ret = sieve_opr_stringlist_read(renv, address,
+ "options", &options);
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* Method operand */
+
+ if ((ret = sieve_opr_string_read(renv, address, "method",
+ &method_uri)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ /* Enforce 0 < importance < 4 (just to be sure) */
+
+ if (importance < 1)
+ importance = 1;
+ else if (importance > 3)
+ importance = 3;
+
+ /* Trace */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
+ sieve_runtime_trace(renv, 0, "notify action");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "notify with uri `%s'",
+ str_sanitize(str_c(method_uri), 80));
+ }
+
+ /* Check operands */
+
+ if ((ret = ext_enotify_runtime_check_operands(renv, method_uri, message,
+ from, options, &method,
+ &method_context)) > 0)
+ {
+ /* Add notify action to the result */
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct sieve_enotify_action, 1);
+ act->method = method;
+ act->method_context = method_context;
+ act->importance = importance;
+ if (message != NULL)
+ act->message = p_strdup(pool, str_c(message));
+ if (from != NULL)
+ act->from = p_strdup(pool, str_c(from));
+
+ if (sieve_result_add_action(renv, this_ext, "notify",
+ &act_notify, slist,
+ (void *)act, 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+ }
+ return ret;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_enotify_action *nact, *nact_other;
+ const struct sieve_enotify_method_def *nmth_def;
+ struct sieve_enotify_env nenv;
+ int result;
+
+ if (act->context == NULL || act_other->context == NULL)
+ return 0;
+
+ nact = (const struct sieve_enotify_action *)act->context;
+ nact_other = (const struct sieve_enotify_action *)act_other->context;
+
+ if (nact->method == NULL || nact->method->def == NULL)
+ return 0;
+
+ nmth_def = nact->method->def;
+ if (nmth_def->action_check_duplicates == NULL)
+ return 0;
+
+ i_zero(&nenv);
+ nenv.svinst = eenv->svinst;
+ nenv.method = nact->method;
+ nenv.ehandler = renv->ehandler;
+ nenv.location = act->location;
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify: ");
+
+ result = nmth_def->action_check_duplicates(&nenv, nact, nact_other);
+
+ event_unref(&nenv.event);
+
+ return result;
+}
+
+/* Result printing */
+
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ const struct sieve_enotify_action *act =
+ (const struct sieve_enotify_action *)action->context;
+ const struct sieve_enotify_method *method;
+
+ method = act->method;
+
+ if (method->def != NULL) {
+ sieve_result_action_printf(
+ rpenv, "send notification with method '%s:':",
+ method->def->identifier);
+
+ if (method->def->action_print != NULL) {
+ struct sieve_enotify_print_env penv;
+
+ i_zero(&penv);
+ penv.result_penv = rpenv;
+
+ method->def->action_print(&penv, act);
+ }
+ }
+}
+
+/* Result execution */
+
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_enotify_action *act =
+ (const struct sieve_enotify_action *)aenv->action->context;
+ const struct sieve_enotify_method *method = act->method;
+ struct sieve_enotify_exec_env nenv;
+ int ret = 0;
+
+ if (method->def != NULL && method->def->action_execute != NULL) {
+ /* Compose log structure */
+ i_zero(&nenv);
+ nenv.svinst = eenv->svinst;
+ nenv.flags = eenv->flags;
+ nenv.method = method;
+ nenv.scriptenv = eenv->scriptenv;
+ nenv.msgdata = eenv->msgdata;
+ nenv.msgctx = aenv->msgctx;
+
+ nenv.ehandler = aenv->ehandler;
+ nenv.event = aenv->event;
+
+ ret = method->def->action_execute(&nenv, act);
+ if (ret >= 0)
+ eenv->exec_status->significant_action_executed = TRUE;
+ }
+
+ return (ret >= 0 ? SIEVE_EXEC_OK : SIEVE_EXEC_TEMP_FAILURE);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.c b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.c
new file mode 100644
index 0000000..e0539f0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.c
@@ -0,0 +1,718 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-enotify-limits.h"
+#include "ext-enotify-common.h"
+
+#include <ctype.h>
+
+/* FIXME: (from draft RFC)
+
+ Header/envelope tests [Sieve] together with Sieve variables can be used to
+ extract the list of users to receive notifications from the incoming email
+ message or its envelope. This is potentially quite dangerous, as this can be
+ used for Deny Of Service attacks on recipients controlled by the message
+ sender. For this reason implementations SHOULD NOT allow use of variables
+ containing values extracted from the email message in the method parameter to
+ the notify action. Note that violation of this SHOULD NOT may result in* the
+ creation of an open relay, i.e. any sender would be able to create specially
+ crafted email messages that would result in notifications delivered to
+ recipients under the control of the sender. In worst case this might result
+ in financial loss by user controlling the Sieve script and/or by recipients
+ of notifications (e.g. if a notification is an SMS message).
+
+ --> This is currently not possible to check.
+ */
+
+/*
+ * Notify capability
+ */
+
+static const char *
+ext_notify_get_methods_string(const struct sieve_extension *ntfy_ext);
+
+const struct sieve_extension_capabilities notify_capabilities = {
+ "notify",
+ ext_notify_get_methods_string
+};
+
+/*
+ * Core notification methods
+ */
+
+extern const struct sieve_enotify_method_def mailto_notify;
+
+/*
+ * Notify method registry
+ */
+
+static const struct sieve_enotify_method *
+ext_enotify_method_register(struct sieve_instance *svinst,
+ struct ext_enotify_context *ectx,
+ const struct sieve_enotify_method_def *nmth_def)
+{
+ struct sieve_enotify_method *nmth;
+ int nmth_id = (int)array_count(&ectx->notify_methods);
+
+ nmth = array_append_space(&ectx->notify_methods);
+ nmth->def = nmth_def;
+ nmth->id = nmth_id;
+ nmth->svinst = svinst;
+
+ if (nmth_def->load != NULL)
+ nmth_def->load(nmth, &nmth->context);
+
+ return nmth;
+}
+
+void ext_enotify_methods_init(struct sieve_instance *svinst,
+ struct ext_enotify_context *ectx)
+{
+ p_array_init(&ectx->notify_methods, default_pool, 4);
+
+ ext_enotify_method_register(svinst, ectx, &mailto_notify);
+}
+
+void ext_enotify_methods_deinit(struct ext_enotify_context *ectx)
+{
+ const struct sieve_enotify_method *methods;
+ unsigned int meth_count, i;
+
+ methods = array_get(&ectx->notify_methods, &meth_count);
+ for (i = 0; i < meth_count; i++) {
+ if (methods[i].def != NULL && methods[i].def->unload != NULL)
+ methods[i].def->unload(&methods[i]);
+ }
+
+ array_free(&ectx->notify_methods);
+}
+
+const struct sieve_enotify_method *
+sieve_enotify_method_register(struct sieve_instance *svinst,
+ const struct sieve_enotify_method_def *nmth_def)
+{
+ const struct sieve_extension *ntfy_ext =
+ sieve_extension_get_by_name(svinst, "enotify");
+
+ if (ntfy_ext != NULL) {
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *) ntfy_ext->context;
+
+ return ext_enotify_method_register(svinst, ectx, nmth_def);
+ }
+ return NULL;
+}
+
+void sieve_enotify_method_unregister(const struct sieve_enotify_method *nmth)
+{
+ struct sieve_instance *svinst = nmth->svinst;
+ const struct sieve_extension *ntfy_ext =
+ sieve_extension_get_by_name(svinst, "enotify");
+
+ if (ntfy_ext != NULL) {
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *) ntfy_ext->context;
+ int nmth_id = nmth->id;
+
+ if (nmth_id >= 0 &&
+ nmth_id < (int)array_count(&ectx->notify_methods)) {
+ struct sieve_enotify_method *nmth_mod =
+ array_idx_modifiable(&ectx->notify_methods,
+ nmth_id);
+
+ nmth_mod->def = NULL;
+ }
+ }
+}
+
+const struct sieve_enotify_method *
+ext_enotify_method_find(const struct sieve_extension *ntfy_ext,
+ const char *identifier)
+{
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *)ntfy_ext->context;
+ unsigned int meth_count, i;
+ const struct sieve_enotify_method *methods;
+
+ methods = array_get(&ectx->notify_methods, &meth_count);
+ for (i = 0; i < meth_count; i++) {
+ if (methods[i].def == NULL)
+ continue;
+
+ if (strcasecmp(methods[i].def->identifier, identifier) == 0)
+ return &methods[i];
+ }
+ return NULL;
+}
+
+static const char *
+ext_notify_get_methods_string(const struct sieve_extension *ntfy_ext)
+{
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *) ntfy_ext->context;
+ unsigned int meth_count, i;
+ const struct sieve_enotify_method *methods;
+ string_t *result = t_str_new(128);
+
+ methods = array_get(&ectx->notify_methods, &meth_count);
+ if (meth_count > 0) {
+ for (i = 0; i < meth_count; i++) {
+ if (str_len(result) > 0)
+ str_append_c(result, ' ');
+ if (methods[i].def != NULL)
+ str_append(result, methods[i].def->identifier);
+ }
+ return str_c(result);
+ }
+ return NULL;
+}
+
+/*
+ * Compile-time argument validation
+ */
+
+static const char *ext_enotify_uri_scheme_parse(const char **uri_p)
+{
+ string_t *scheme = t_str_new(EXT_ENOTIFY_MAX_SCHEME_LEN);
+ const char *p = *uri_p;
+ unsigned int len = 0;
+
+ /* RFC 3968:
+
+ scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+
+ FIXME: we do not allow '%' in schemes. Is this correct?
+ */
+
+ if (!i_isalpha(*p))
+ return NULL;
+
+ str_append_c(scheme, *p);
+ p++;
+
+ while (*p != '\0' && len < EXT_ENOTIFY_MAX_SCHEME_LEN) {
+ if (!i_isalnum(*p) && *p != '+' && *p != '-' && *p != '.')
+ break;
+
+ str_append_c(scheme, *p);
+ p++;
+ len++;
+ }
+
+ if (*p != ':')
+ return NULL;
+ p++;
+
+ *uri_p = p;
+ return str_c(scheme);
+}
+
+static bool
+ext_enotify_option_parse(struct sieve_enotify_env *nenv,
+ const char *option, bool name_only,
+ const char **opt_name_r, const char **opt_value_r)
+{
+ const char *p = option;
+
+ /* "<optionname>=<value>".
+
+ l-d = ALPHA / DIGIT
+ l-d-p = l-d / "." / "-" / "_"
+ optionname = l-d *l-d-p
+ value = *(%x01-09 / %x0B-0C / %x0E-FF)
+ */
+
+ /*
+ * Parse option name
+ */
+
+ /* optionname = l-d *l-d-p
+ */
+
+ /* Explicitly report empty option as such */
+ if (*p == '\0') {
+ sieve_enotify_error(nenv, "empty option specified");
+ return FALSE;
+ }
+
+ /* l-d = ALPHA / DIGIT */
+ if (i_isalnum(*p)) {
+ p++;
+
+ /* l-d-p = l-d / "." / "-" / "_" */
+ while (i_isalnum(*p) || *p == '.' || *p == '-' || *p == '_')
+ p++;
+ }
+
+ /* Parsing must end at '=' and we must parse at least one character */
+ if (*p != '=' || p == option) {
+ sieve_enotify_error(
+ nenv, "invalid option name specified in option '%s'",
+ str_sanitize(option, 80));
+ return FALSE;
+ }
+
+ /* Assign option name */
+ if (opt_name_r != NULL)
+ *opt_name_r = t_strdup_until(option, p);
+
+ /* Skip '=' */
+ p++;
+
+ /* Exit now if only the option name is of interest */
+ if (name_only)
+ return TRUE;
+
+ /*
+ * Parse option value
+ */
+
+ /* value = *(%x01-09 / %x0B-0C / %x0E-FF) */
+ while (*p != '\0' && *p != 0x0A && *p != 0x0D)
+ p++;
+
+ /* Parse must end at end of string */
+ if (*p != '\0') {
+ sieve_enotify_error(
+ nenv, "notify command: "
+ "invalid option value specified in option '%s'",
+ str_sanitize(option, 80));
+ return FALSE;
+ }
+
+ /* Assign option value */
+ if (opt_value_r != NULL)
+ *opt_value_r = p;
+
+ return TRUE;
+}
+
+struct _ext_enotify_option_check_context {
+ struct sieve_instance *svinst;
+ struct sieve_validator *valdtr;
+ const struct sieve_enotify_method *method;
+};
+
+static int
+_ext_enotify_option_check(void *context, struct sieve_ast_argument *arg)
+{
+ struct _ext_enotify_option_check_context *optn_context =
+ (struct _ext_enotify_option_check_context *) context;
+ struct sieve_validator *valdtr = optn_context->valdtr;
+ const struct sieve_enotify_method *method = optn_context->method;
+ struct sieve_enotify_env nenv;
+ const char *option = sieve_ast_argument_strc(arg);
+ const char *opt_name = NULL, *opt_value = NULL;
+ bool check = TRUE;
+ int result = 1;
+
+ /* Compose log structure */
+ i_zero(&nenv);
+ nenv.svinst = optn_context->svinst;
+ nenv.method = method;
+ nenv.ehandler = sieve_validator_error_handler(valdtr);
+ nenv.location = sieve_error_script_location(
+ sieve_validator_script(valdtr), arg->source_line);
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify command: ");
+
+ /* Parse option */
+ if (!sieve_argument_is_string_literal(arg)) {
+ /* Variable string: partial option parse
+
+ If the string item is not a string literal, it cannot be
+ validated fully at compile time. We can however check whether
+ the '=' is in the string specification and whether the part
+ before the '=' is a valid option name. In that case, the
+ method option check function is called with the value
+ parameter equal to NULL, meaning that it should only check
+ the validity of the option itself and not the assigned value.
+ */
+ if (!ext_enotify_option_parse(NULL, option, TRUE,
+ &opt_name, &opt_value))
+ check = FALSE;
+ } else {
+ /* Literal string: full option parse */
+ if (!ext_enotify_option_parse(&nenv, option, FALSE,
+ &opt_name, &opt_value))
+ result = -1;
+ }
+
+ /* Call method's option check function */
+ if (result > 0 && check && method->def != NULL &&
+ method->def->compile_check_option != NULL) {
+ result = (method->def->compile_check_option(&nenv, opt_name,
+ opt_value) ?
+ 1 : -1 );
+ }
+
+ event_unref(&nenv.event);
+ return result;
+}
+
+bool ext_enotify_compile_check_arguments(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *uri_arg,
+ struct sieve_ast_argument *msg_arg,
+ struct sieve_ast_argument *from_arg,
+ struct sieve_ast_argument *options_arg)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_instance *svinst = this_ext->svinst;
+ const char *uri = sieve_ast_argument_strc(uri_arg);
+ const char *scheme;
+ const struct sieve_enotify_method *method;
+ struct sieve_enotify_env nenv;
+ bool result = TRUE;
+
+ /* If the uri string is not a constant literal, we cannot determine
+ which method is used, so we bail out successfully and defer checking
+ to runtime.
+ */
+ if (!sieve_argument_is_string_literal(uri_arg))
+ return TRUE;
+
+ /* Parse scheme part of URI */
+ if ((scheme = ext_enotify_uri_scheme_parse(&uri)) == NULL) {
+ sieve_argument_validate_error(
+ valdtr, uri_arg, "notify command: "
+ "invalid scheme part for method URI '%s'",
+ str_sanitize(sieve_ast_argument_strc(uri_arg), 80));
+ return FALSE;
+ }
+
+ /* Find used method with the parsed scheme identifier */
+ if ((method = ext_enotify_method_find(this_ext, scheme)) == NULL) {
+ sieve_argument_validate_error(
+ valdtr, uri_arg, "notify command: "
+ "invalid method '%s'", scheme);
+ return FALSE;
+ }
+
+ if (method->def == NULL)
+ return TRUE;
+
+ /* Compose log structure */
+ i_zero(&nenv);
+ nenv.svinst = svinst;
+ nenv.method = method;
+
+ /* Check URI itself */
+ if (result && method->def->compile_check_uri != NULL) {
+ /* Set log location to location of URI argument */
+ nenv.ehandler = sieve_validator_error_handler(valdtr);
+ nenv.location = sieve_error_script_location(
+ sieve_validator_script(valdtr), uri_arg->source_line);
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify command: ");
+
+ /* Execute method check function */
+ result = method->def->compile_check_uri(
+ &nenv, sieve_ast_argument_strc(uri_arg), uri);
+ }
+
+ /* Check :message argument */
+ if (result && msg_arg != NULL &&
+ sieve_argument_is_string_literal(msg_arg) &&
+ method->def->compile_check_message != NULL ) {
+ /* Set log location to location of :message argument */
+ event_unref(&nenv.event);
+ nenv.ehandler = sieve_validator_error_handler(valdtr);
+ nenv.location = sieve_error_script_location(
+ sieve_validator_script(valdtr), msg_arg->source_line);
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify command: ");
+
+ /* Execute method check function */
+ result = method->def->compile_check_message(
+ &nenv, sieve_ast_argument_str(msg_arg));
+ }
+
+ /* Check :from argument */
+ if (result && from_arg != NULL &&
+ sieve_argument_is_string_literal(from_arg) &&
+ method->def->compile_check_from != NULL ) {
+ /* Set log location to location of :from argument */
+ event_unref(&nenv.event);
+ nenv.ehandler = sieve_validator_error_handler(valdtr);
+ nenv.location = sieve_error_script_location(
+ sieve_validator_script(valdtr), from_arg->source_line);
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify command: ");
+
+ /* Execute method check function */
+ result = method->def->compile_check_from(
+ &nenv, sieve_ast_argument_str(from_arg));
+ }
+
+ event_unref(&nenv.event);
+
+ /* Check :options argument */
+ if (result && options_arg != NULL) {
+ struct sieve_ast_argument *option = options_arg;
+ struct _ext_enotify_option_check_context optn_context = {
+ svinst, valdtr, method };
+
+ /* Parse and check options */
+ result = (sieve_ast_stringlist_map(
+ &option, (void *) &optn_context,
+ _ext_enotify_option_check) > 0);
+
+ /* Discard argument if options are not accepted by method */
+ if (result && method->def->compile_check_option == NULL) {
+ sieve_argument_validate_warning(
+ valdtr, options_arg, "notify command: "
+ "method '%s' accepts no options", scheme);
+ (void)sieve_ast_arguments_detach(options_arg, 1);
+ }
+ }
+ return result;
+}
+
+/*
+ * Runtime operand checking
+ */
+
+bool ext_enotify_runtime_method_validate(const struct sieve_runtime_env *renv,
+ string_t *method_uri)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ const struct sieve_enotify_method *method;
+ const char *uri = str_c(method_uri);
+ const char *scheme;
+ bool result = TRUE;
+
+ /* Get the method */
+
+ if ((scheme = ext_enotify_uri_scheme_parse(&uri)) == NULL)
+ return FALSE;
+ if ((method = ext_enotify_method_find(this_ext, scheme)) == NULL)
+ return FALSE;
+
+ /* Validate the provided URI */
+
+ if (method->def != NULL && method->def->runtime_check_uri != NULL) {
+ struct sieve_enotify_env nenv;
+
+ i_zero(&nenv);
+ nenv.svinst = eenv->svinst;
+ nenv.method = method;
+ nenv.ehandler = renv->ehandler;
+ nenv.location = sieve_runtime_get_full_command_location(renv),
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event,
+ "valid_notify_method test: ");
+
+ /* Use the method check function to validate the URI */
+ result = method->def->runtime_check_uri(
+ &nenv, str_c(method_uri), uri);
+
+ event_unref(&nenv.event);
+ }
+
+ return result;
+}
+
+static const struct
+sieve_enotify_method *ext_enotify_get_method(
+ const struct sieve_runtime_env *renv, string_t *method_uri,
+ const char **uri_body_r)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ const struct sieve_enotify_method *method;
+ const char *uri = str_c(method_uri);
+ const char *scheme;
+
+ /* Parse part before ':' of the uri (the scheme) and use it to identify
+ notify method.
+ */
+ if ((scheme = ext_enotify_uri_scheme_parse(&uri)) == NULL) {
+ sieve_runtime_error(
+ renv, NULL, "invalid scheme part for method URI '%s'",
+ str_sanitize(str_c(method_uri), 80));
+ return NULL;
+ }
+
+ /* Find the notify method */
+ if ((method = ext_enotify_method_find(this_ext, scheme)) == NULL) {
+ sieve_runtime_error(renv, NULL, "invalid notify method '%s'",
+ scheme);
+ return NULL;
+ }
+
+ /* Return the parse pointer and the found method */
+ *uri_body_r = uri;
+ return method;
+}
+
+const char *
+ext_enotify_runtime_get_method_capability(const struct sieve_runtime_env *renv,
+ string_t *method_uri,
+ const char *capability)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_enotify_method *method;
+ const char *uri_body;
+ const char *result = NULL;
+
+ /* Get method */
+ method = ext_enotify_get_method(renv, method_uri, &uri_body);
+ if ( method == NULL ) return NULL;
+
+ /* Get requested capability */
+ if (method->def != NULL &&
+ method->def->runtime_get_method_capability != NULL) {
+ struct sieve_enotify_env nenv;
+
+ i_zero(&nenv);
+ nenv.svinst = eenv->svinst;
+ nenv.method = method;
+ nenv.ehandler = renv->ehandler;
+ nenv.location = sieve_runtime_get_full_command_location(renv),
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event,
+ "notify_method_capability test: ");
+
+ /* Execute method function to acquire capability value */
+ result = method->def->runtime_get_method_capability(
+ &nenv, str_c(method_uri), uri_body, capability);
+
+ event_unref(&nenv.event);
+ }
+
+ return result;
+}
+
+int ext_enotify_runtime_check_operands(
+ const struct sieve_runtime_env *renv, string_t *method_uri,
+ string_t *message, string_t *from, struct sieve_stringlist *options,
+ const struct sieve_enotify_method **method_r, void **method_context)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_enotify_method *method;
+ const char *uri_body;
+
+ /* Get method */
+ method = ext_enotify_get_method(renv, method_uri, &uri_body);
+ if (method == NULL)
+ return SIEVE_EXEC_FAILURE;
+
+ /* Check provided operands */
+ if (method->def != NULL &&
+ method->def->runtime_check_operands != NULL) {
+ struct sieve_enotify_env nenv;
+ int result = SIEVE_EXEC_OK;
+
+ i_zero(&nenv);
+ nenv.svinst = eenv->svinst;
+ nenv.method = method;
+ nenv.ehandler = renv->ehandler;
+ nenv.location = sieve_runtime_get_full_command_location(renv),
+ nenv.event = event_create(nenv.svinst->event);
+ event_set_append_log_prefix(nenv.event, "notify_action: ");
+
+ /* Execute check function */
+ if (method->def->runtime_check_operands(
+ &nenv, str_c(method_uri), uri_body, message, from,
+ sieve_result_pool(renv->result), method_context)) {
+
+ /* Check any provided options */
+ if (options != NULL) {
+ string_t *option = NULL;
+ int ret;
+
+ /* Iterate through all provided options */
+ while ((ret = sieve_stringlist_next_item(
+ options, &option)) > 0) {
+ const char *opt_name = NULL;
+ const char *opt_value = NULL;
+
+ /* Parse option into <optionname> and
+ <value> */
+ if (ext_enotify_option_parse(
+ &nenv, str_c(option), FALSE,
+ &opt_name, &opt_value)) {
+
+ /* Set option */
+ if (method->def->runtime_set_option != NULL) {
+ (void)method->def->runtime_set_option(
+ &nenv, *method_context,
+ opt_name, opt_value);
+ }
+ }
+ }
+
+ /* Check for binary corruptions encountered
+ during string list iteration */
+ if (ret >= 0) {
+ *method_r = method;
+ } else {
+ /* Binary corrupt */
+ sieve_runtime_trace_error(
+ renv, "invalid item in options string list");
+ result = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ } else {
+ /* No options */
+ *method_r = method;
+ }
+
+ } else {
+ /* Operand check failed */
+ result = SIEVE_EXEC_FAILURE;
+ }
+
+ event_unref(&nenv.event);
+ return result;
+ }
+
+ /* No check function defined: a most unlikely situation */
+ *method_context = NULL;
+ *method_r = method;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Notify method printing
+ */
+
+void sieve_enotify_method_printf(const struct sieve_enotify_print_env *penv,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_result_vprintf(penv->result_penv, fmt, args);
+ va_end(args);
+}
+
+/*
+ * Action execution
+ */
+
+struct event_passthrough *
+sieve_enotify_create_finish_event(const struct sieve_enotify_exec_env *nenv)
+{
+ struct event_passthrough *e =
+ event_create_passthrough(nenv->event)->
+ set_name("sieve_action_finished");
+
+ return e;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.h b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.h
new file mode 100644
index 0000000..ec43202
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-common.h
@@ -0,0 +1,122 @@
+#ifndef EXT_ENOTIFY_COMMON_H
+#define EXT_ENOTIFY_COMMON_H
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ext-variables.h"
+
+#include "sieve-ext-enotify.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def enotify_extension;
+extern const struct sieve_extension_capabilities notify_capabilities;
+
+struct ext_enotify_context {
+ const struct sieve_extension *var_ext;
+ ARRAY(struct sieve_enotify_method) notify_methods;
+};
+
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def notify_command;
+
+/* Codes for optional arguments */
+
+enum cmd_notify_optional {
+ CMD_NOTIFY_OPT_END,
+ CMD_NOTIFY_OPT_FROM,
+ CMD_NOTIFY_OPT_OPTIONS,
+ CMD_NOTIFY_OPT_MESSAGE,
+ CMD_NOTIFY_OPT_IMPORTANCE
+};
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def valid_notify_method_test;
+extern const struct sieve_command_def notify_method_capability_test;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def notify_operation;
+extern const struct sieve_operation_def valid_notify_method_operation;
+extern const struct sieve_operation_def notify_method_capability_operation;
+
+enum ext_variables_opcode {
+ EXT_ENOTIFY_OPERATION_NOTIFY,
+ EXT_ENOTIFY_OPERATION_VALID_NOTIFY_METHOD,
+ EXT_ENOTIFY_OPERATION_NOTIFY_METHOD_CAPABILITY
+};
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def encodeurl_operand;
+
+/*
+ * Modifiers
+ */
+
+extern const struct sieve_variables_modifier_def encodeurl_modifier;
+
+/*
+ * Notify methods
+ */
+
+void ext_enotify_methods_init(struct sieve_instance *svinst,
+ struct ext_enotify_context *ectx);
+void ext_enotify_methods_deinit(struct ext_enotify_context *ectx);
+
+const struct sieve_enotify_method *
+ext_enotify_method_find(const struct sieve_extension *ntfy_ext,
+ const char *identifier);
+
+/*
+ * Validation
+ */
+
+bool ext_enotify_compile_check_arguments(
+ struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *uri_arg, struct sieve_ast_argument *msg_arg,
+ struct sieve_ast_argument *from_arg,
+ struct sieve_ast_argument *options_arg);
+
+/*
+ * Runtime
+ */
+
+bool ext_enotify_runtime_method_validate(const struct sieve_runtime_env *renv,
+ string_t *method_uri);
+
+const char *
+ext_enotify_runtime_get_method_capability(const struct sieve_runtime_env *renv,
+ string_t *method_uri,
+ const char *capability);
+
+int ext_enotify_runtime_check_operands(
+ const struct sieve_runtime_env *renv, string_t *method_uri,
+ string_t *message, string_t *from, struct sieve_stringlist *options,
+ const struct sieve_enotify_method **method_r, void **method_context);
+
+/*
+ * Method printing
+ */
+
+struct sieve_enotify_print_env {
+ const struct sieve_result_print_env *result_penv;
+};
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-limits.h b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-limits.h
new file mode 100644
index 0000000..aac48df
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify-limits.h
@@ -0,0 +1,6 @@
+#ifndef EXT_ENOTIFY_LIMITS_H
+#define EXT_ENOTIFY_LIMITS_H
+
+#define EXT_ENOTIFY_MAX_SCHEME_LEN 32
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify.c b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify.c
new file mode 100644
index 0000000..df479b3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/ext-enotify.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension enotify
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5435
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_enotify_operations[] = {
+ &notify_operation,
+ &valid_notify_method_operation,
+ &notify_method_capability_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_enotify_load(const struct sieve_extension *ext, void **context);
+static void ext_enotify_unload(const struct sieve_extension *ext);
+static bool ext_enotify_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def enotify_extension = {
+ .name = "enotify",
+ .load = ext_enotify_load,
+ .unload = ext_enotify_unload,
+ .validator_load = ext_enotify_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_enotify_operations),
+ SIEVE_EXT_DEFINE_OPERAND(encodeurl_operand)
+};
+
+static bool ext_enotify_load(const struct sieve_extension *ext, void **context)
+{
+ struct ext_enotify_context *ectx;
+
+ if ( *context != NULL ) {
+ ext_enotify_unload(ext);
+ }
+
+ ectx = i_new(struct ext_enotify_context, 1);
+ ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+ *context = (void *) ectx;
+
+ ext_enotify_methods_init(ext->svinst, ectx);
+
+ sieve_extension_capabilities_register(ext, &notify_capabilities);
+
+ return TRUE;
+}
+
+static void ext_enotify_unload(const struct sieve_extension *ext)
+{
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *) ext->context;
+
+ ext_enotify_methods_deinit(ectx);
+
+ i_free(ectx);
+}
+
+static bool ext_enotify_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ struct ext_enotify_context *ectx =
+ (struct ext_enotify_context *) ext->context;
+
+ /* Register new commands */
+ sieve_validator_register_command(valdtr, ext, &notify_command);
+ sieve_validator_register_command(valdtr, ext, &valid_notify_method_test);
+ sieve_validator_register_command(valdtr, ext, &notify_method_capability_test);
+
+ /* Register new set modifier for variables extension */
+ sieve_variables_modifier_register
+ (ectx->var_ext, valdtr, ext, &encodeurl_modifier);
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.am b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.am
new file mode 100644
index 0000000..83f129c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_enotify_mailto.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/.. \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_enotify_mailto_la_SOURCES = \
+ uri-mailto.c \
+ ntfy-mailto.c
+
+noinst_HEADERS = \
+ uri-mailto.h
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in
new file mode 100644
index 0000000..49aa41a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in
@@ -0,0 +1,687 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/enotify/mailto
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_enotify_mailto_la_LIBADD =
+am_libsieve_ext_enotify_mailto_la_OBJECTS = uri-mailto.lo \
+ ntfy-mailto.lo
+libsieve_ext_enotify_mailto_la_OBJECTS = \
+ $(am_libsieve_ext_enotify_mailto_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ntfy-mailto.Plo \
+ ./$(DEPDIR)/uri-mailto.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_enotify_mailto_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_enotify_mailto_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_enotify_mailto.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/.. \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_enotify_mailto_la_SOURCES = \
+ uri-mailto.c \
+ ntfy-mailto.c
+
+noinst_HEADERS = \
+ uri-mailto.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/enotify/mailto/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/enotify/mailto/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_enotify_mailto.la: $(libsieve_ext_enotify_mailto_la_OBJECTS) $(libsieve_ext_enotify_mailto_la_DEPENDENCIES) $(EXTRA_libsieve_ext_enotify_mailto_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_enotify_mailto_la_OBJECTS) $(libsieve_ext_enotify_mailto_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntfy-mailto.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uri-mailto.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ntfy-mailto.Plo
+ -rm -f ./$(DEPDIR)/uri-mailto.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ntfy-mailto.Plo
+ -rm -f ./$(DEPDIR)/uri-mailto.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
new file mode 100644
index 0000000..4e104d1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
@@ -0,0 +1,794 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Notify method mailto
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5436
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
+ * draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
+ * when it matures. This requires modifications to the address parser (no
+ * whitespace allowed within the address itself) and UTF-8 support will be
+ * required in the URL.
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-address.h"
+#include "sieve-address-source.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+#include "sieve-settings.h"
+
+#include "sieve-ext-enotify.h"
+
+#include "rfc2822.h"
+
+#include "uri-mailto.h"
+
+/*
+ * Configuration
+ */
+
+#define NTFY_MAILTO_MAX_RECIPIENTS 8
+#define NTFY_MAILTO_MAX_HEADERS 16
+#define NTFY_MAILTO_MAX_SUBJECT 256
+
+/*
+ * Mailto notification configuration
+ */
+
+struct ntfy_mailto_config {
+ pool_t pool;
+ struct sieve_address_source envelope_from;
+};
+
+/*
+ * Mailto notification method
+ */
+
+static bool ntfy_mailto_load
+ (const struct sieve_enotify_method *nmth, void **context);
+static void ntfy_mailto_unload
+ (const struct sieve_enotify_method *nmth);
+
+static bool ntfy_mailto_compile_check_uri
+ (const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
+static bool ntfy_mailto_compile_check_from
+ (const struct sieve_enotify_env *nenv, string_t *from);
+
+static const char *ntfy_mailto_runtime_get_notify_capability
+ (const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body,
+ const char *capability);
+static bool ntfy_mailto_runtime_check_uri
+ (const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
+static bool ntfy_mailto_runtime_check_operands
+ (const struct sieve_enotify_env *nenv, const char *uri,const char *uri_body,
+ string_t *message, string_t *from, pool_t context_pool,
+ void **method_context);
+
+static int ntfy_mailto_action_check_duplicates
+ (const struct sieve_enotify_env *nenv,
+ const struct sieve_enotify_action *nact,
+ const struct sieve_enotify_action *nact_other);
+
+static void ntfy_mailto_action_print
+ (const struct sieve_enotify_print_env *penv,
+ const struct sieve_enotify_action *nact);
+
+static int ntfy_mailto_action_execute
+ (const struct sieve_enotify_exec_env *nenv,
+ const struct sieve_enotify_action *nact);
+
+const struct sieve_enotify_method_def mailto_notify = {
+ "mailto",
+ ntfy_mailto_load,
+ ntfy_mailto_unload,
+ ntfy_mailto_compile_check_uri,
+ NULL,
+ ntfy_mailto_compile_check_from,
+ NULL,
+ ntfy_mailto_runtime_check_uri,
+ ntfy_mailto_runtime_get_notify_capability,
+ ntfy_mailto_runtime_check_operands,
+ NULL,
+ ntfy_mailto_action_check_duplicates,
+ ntfy_mailto_action_print,
+ ntfy_mailto_action_execute
+};
+
+/*
+ * Reserved and unique headers
+ */
+
+static const char *_reserved_headers[] = {
+ "auto-submitted",
+ "received",
+ "message-id",
+ "data",
+ "bcc",
+ "in-reply-to",
+ "references",
+ "resent-date",
+ "resent-from",
+ "resent-sender",
+ "resent-to",
+ "resent-cc",
+ "resent-bcc",
+ "resent-msg-id",
+ "from",
+ "sender",
+ NULL
+};
+
+static const char *_unique_headers[] = {
+ "reply-to",
+ NULL
+};
+
+/*
+ * Method context data
+ */
+
+struct ntfy_mailto_context {
+ struct uri_mailto *uri;
+ const struct smtp_address *from_address;
+};
+
+/*
+ * Method registration
+ */
+
+static bool ntfy_mailto_load
+(const struct sieve_enotify_method *nmth, void **context)
+{
+ struct sieve_instance *svinst = nmth->svinst;
+ struct ntfy_mailto_config *config;
+ pool_t pool;
+
+ if ( *context != NULL ) {
+ ntfy_mailto_unload(nmth);
+ }
+
+ pool = pool_alloconly_create("ntfy_mailto_config", 256);
+ config = p_new(pool, struct ntfy_mailto_config, 1);
+ config->pool = pool;
+
+ (void)sieve_address_source_parse_from_setting (svinst,
+ config->pool, "sieve_notify_mailto_envelope_from",
+ &config->envelope_from);
+
+ *context = (void *) config;
+
+ return TRUE;
+}
+
+static void ntfy_mailto_unload
+(const struct sieve_enotify_method *nmth)
+{
+ struct ntfy_mailto_config *config =
+ (struct ntfy_mailto_config *) nmth->context;
+
+ pool_unref(&config->pool);
+}
+
+/*
+ * URI parsing
+ */
+
+struct ntfy_mailto_uri_env {
+ const struct sieve_enotify_env *nenv;
+
+ struct event *event;
+
+ struct uri_mailto_log uri_log;
+};
+
+static void ATTR_FORMAT(5, 0)
+ntfy_mailto_uri_logv(void *context, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, va_list args)
+{
+ struct ntfy_mailto_uri_env *nmuenv = context;
+ const struct sieve_enotify_env *nenv = nmuenv->nenv;
+
+ sieve_event_logv(nenv->svinst, nenv->ehandler, nmuenv->event,
+ log_type, csrc_filename, csrc_linenum,
+ nenv->location, 0, fmt, args);
+}
+
+static void
+ntfy_mailto_uri_env_init(struct ntfy_mailto_uri_env *nmuenv,
+ const struct sieve_enotify_env *nenv)
+{
+ i_zero(nmuenv);
+ nmuenv->nenv = nenv;
+ nmuenv->event = event_create(nenv->event);
+ event_set_append_log_prefix(nmuenv->event, "mailto URI: ");
+
+ nmuenv->uri_log.context = nmuenv;
+ nmuenv->uri_log.logv = ntfy_mailto_uri_logv;
+}
+
+static void
+ntfy_mailto_uri_env_deinit(struct ntfy_mailto_uri_env *nmuenv)
+{
+ event_unref(&nmuenv->event);
+}
+
+/*
+ * Validation
+ */
+
+
+static bool ntfy_mailto_compile_check_uri
+(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
+ const char *uri_body)
+{
+ struct ntfy_mailto_uri_env nmuenv;
+ bool result;
+
+ ntfy_mailto_uri_env_init(&nmuenv, nenv);
+ result = uri_mailto_validate(
+ uri_body, _reserved_headers, _unique_headers,
+ NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS,
+ &nmuenv.uri_log);
+ ntfy_mailto_uri_env_deinit(&nmuenv);
+
+ return result;
+}
+
+static bool ntfy_mailto_compile_check_from
+(const struct sieve_enotify_env *nenv, string_t *from)
+{
+ const char *error;
+ bool result = FALSE;
+
+ T_BEGIN {
+ result = sieve_address_validate_str(from, &error);
+ if ( !result ) {
+ sieve_enotify_error(nenv,
+ "specified :from address '%s' is invalid for "
+ "the mailto method: %s",
+ str_sanitize(str_c(from), 128), error);
+ }
+ } T_END;
+
+ return result;
+}
+
+/*
+ * Runtime
+ */
+
+struct ntfy_mailto_runtime_env {
+ const struct sieve_enotify_env *nenv;
+
+ struct event *event;
+};
+
+static const char *ntfy_mailto_runtime_get_notify_capability
+(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
+ const char *uri_body, const char *capability)
+{
+ if ( !uri_mailto_validate(uri_body, _reserved_headers, _unique_headers,
+ NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL) ) {
+ return NULL;
+ }
+
+ if ( strcasecmp(capability, "online") == 0 )
+ return "maybe";
+
+ return NULL;
+}
+
+static bool ntfy_mailto_runtime_check_uri
+(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
+ const char *uri_body)
+{
+ return uri_mailto_validate
+ (uri_body, _reserved_headers, _unique_headers,
+ NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL);
+}
+
+static bool ntfy_mailto_runtime_check_operands
+(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
+ const char *uri_body, string_t *message ATTR_UNUSED, string_t *from,
+ pool_t context_pool, void **method_context)
+{
+ struct ntfy_mailto_context *mtctx;
+ struct uri_mailto *parsed_uri;
+ const struct smtp_address *address;
+ struct ntfy_mailto_uri_env nmuenv;
+ const char *error;
+
+ /* Need to create context before validation to have arrays present */
+ mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
+
+ /* Validate :from */
+ if ( from != NULL ) {
+ T_BEGIN {
+ address = sieve_address_parse_str(from, &error);
+ if ( address == NULL ) {
+ sieve_enotify_error(nenv,
+ "specified :from address '%s' is invalid for "
+ "the mailto method: %s",
+ str_sanitize(str_c(from), 128), error);
+ } else
+ mtctx->from_address =
+ smtp_address_clone(context_pool, address);
+ } T_END;
+
+ if ( address == NULL ) return FALSE;
+ }
+
+ ntfy_mailto_uri_env_init(&nmuenv, nenv);
+ parsed_uri = uri_mailto_parse(uri_body, context_pool,
+ _reserved_headers, _unique_headers,
+ NTFY_MAILTO_MAX_RECIPIENTS,
+ NTFY_MAILTO_MAX_HEADERS,
+ &nmuenv.uri_log);
+ ntfy_mailto_uri_env_deinit(&nmuenv);
+
+ if (parsed_uri == NULL)
+ return FALSE;
+
+ mtctx->uri = parsed_uri;
+ *method_context = (void *) mtctx;
+ return TRUE;
+}
+
+/*
+ * Action duplicates
+ */
+
+static int ntfy_mailto_action_check_duplicates
+(const struct sieve_enotify_env *nenv ATTR_UNUSED,
+ const struct sieve_enotify_action *nact,
+ const struct sieve_enotify_action *nact_other)
+{
+ struct ntfy_mailto_context *mtctx =
+ (struct ntfy_mailto_context *) nact->method_context;
+ struct ntfy_mailto_context *mtctx_other =
+ (struct ntfy_mailto_context *) nact_other->method_context;
+ const struct uri_mailto_recipient *new_rcpts, *old_rcpts;
+ unsigned int new_count, old_count, i, j;
+ unsigned int del_start = 0, del_len = 0;
+
+ new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
+ old_rcpts = array_get(&mtctx_other->uri->recipients, &old_count);
+
+ for ( i = 0; i < new_count; i++ ) {
+ for ( j = 0; j < old_count; j++ ) {
+ if ( smtp_address_equals
+ (new_rcpts[i].address, old_rcpts[j].address) )
+ break;
+ }
+
+ if ( j == old_count ) {
+ /* Not duplicate */
+ if ( del_len > 0 ) {
+ /* Perform pending deletion */
+ array_delete(&mtctx->uri->recipients, del_start, del_len);
+
+ /* Make sure the loop integrity is maintained */
+ i -= del_len;
+ new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
+ }
+ del_len = 0;
+ } else {
+ /* Mark deletion */
+ if ( del_len == 0 )
+ del_start = i;
+ del_len++;
+ }
+ }
+
+ /* Perform pending deletion */
+ if ( del_len > 0 ) {
+ array_delete(&mtctx->uri->recipients, del_start, del_len);
+ }
+
+ return ( array_count(&mtctx->uri->recipients) > 0 ? 0 : 1 );
+}
+
+/*
+ * Action printing
+ */
+
+static void ntfy_mailto_action_print
+(const struct sieve_enotify_print_env *penv,
+ const struct sieve_enotify_action *nact)
+{
+ unsigned int count, i;
+ const struct uri_mailto_recipient *recipients;
+ const struct uri_mailto_header_field *headers;
+ struct ntfy_mailto_context *mtctx =
+ (struct ntfy_mailto_context *) nact->method_context;
+
+ /* Print main method parameters */
+
+ sieve_enotify_method_printf
+ (penv, " => importance : %llu\n",
+ (unsigned long long)nact->importance);
+
+ if ( nact->message != NULL )
+ sieve_enotify_method_printf
+ (penv, " => subject : %s\n", nact->message);
+ else if ( mtctx->uri->subject != NULL )
+ sieve_enotify_method_printf
+ (penv, " => subject : %s\n", mtctx->uri->subject);
+
+ if ( nact->from != NULL )
+ sieve_enotify_method_printf
+ (penv, " => from : %s\n", nact->from);
+
+ /* Print mailto: recipients */
+
+ sieve_enotify_method_printf(penv, " => recipients :\n" );
+
+ recipients = array_get(&mtctx->uri->recipients, &count);
+ if ( count == 0 ) {
+ sieve_enotify_method_printf(penv, " NONE, action has no effect\n");
+ } else {
+ for ( i = 0; i < count; i++ ) {
+ if ( recipients[i].carbon_copy )
+ sieve_enotify_method_printf
+ (penv, " + Cc: %s\n", recipients[i].full);
+ else
+ sieve_enotify_method_printf
+ (penv, " + To: %s\n", recipients[i].full);
+ }
+ }
+
+ /* Print accepted headers for notification message */
+
+ headers = array_get(&mtctx->uri->headers, &count);
+ if ( count > 0 ) {
+ sieve_enotify_method_printf(penv, " => headers :\n" );
+ for ( i = 0; i < count; i++ ) {
+ sieve_enotify_method_printf(penv, " + %s: %s\n",
+ headers[i].name, headers[i].body);
+ }
+ }
+
+ /* Print body for notification message */
+
+ if ( mtctx->uri->body != NULL )
+ sieve_enotify_method_printf
+ (penv, " => body : \n--\n%s\n--\n", mtctx->uri->body);
+
+ /* Finish output with an empty line */
+
+ sieve_enotify_method_printf(penv, "\n");
+}
+
+/*
+ * Action execution
+ */
+
+static bool _contains_8bit(const char *msg)
+{
+ const unsigned char *s = (const unsigned char *)msg;
+
+ for (; *s != '\0'; s++) {
+ if ((*s & 0x80) != 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int ntfy_mailto_send
+(const struct sieve_enotify_exec_env *nenv,
+ const struct sieve_enotify_action *nact,
+ const struct smtp_address *owner_email)
+{
+ struct sieve_instance *svinst = nenv->svinst;
+ const struct sieve_message_data *msgdata = nenv->msgdata;
+ const struct sieve_script_env *senv = nenv->scriptenv;
+ struct ntfy_mailto_context *mtctx =
+ (struct ntfy_mailto_context *) nact->method_context;
+ struct ntfy_mailto_config *mth_config =
+ (struct ntfy_mailto_config *)nenv->method->context;
+ struct sieve_address_source env_from =
+ mth_config->envelope_from;
+ const char *from = NULL;
+ const struct smtp_address *from_smtp = NULL;
+ const char *subject = mtctx->uri->subject;
+ const char *body = mtctx->uri->body;
+ string_t *to, *cc, *all;
+ const struct uri_mailto_recipient *recipients;
+ const struct uri_mailto_header_field *headers;
+ struct sieve_smtp_context *sctx;
+ struct ostream *output;
+ string_t *msg;
+ unsigned int count, i, hcount, h;
+ const char *outmsgid, *error;
+ int ret;
+
+ /* Get recipients */
+ recipients = array_get(&mtctx->uri->recipients, &count);
+ if ( count == 0 ) {
+ sieve_enotify_warning(nenv,
+ "notify mailto uri specifies no recipients; action has no effect");
+ return 0;
+ }
+
+ /* Just to be sure */
+ if ( !sieve_smtp_available(senv) ) {
+ sieve_enotify_global_warning(nenv,
+ "notify mailto method has no means to send mail");
+ return 0;
+ }
+
+ /* Determine which sender to use
+
+ From RFC 5436, Section 2.3:
+
+ The ":from" tag overrides the default sender of the notification
+ message. "Sender", here, refers to the value used in the [RFC5322]
+ "From" header. Implementations MAY also use this value in the
+ [RFC5321] "MAIL FROM" command (the "envelope sender"), or they may
+ prefer to establish a mailbox that receives bounces from notification
+ messages.
+ */
+ if ( (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 ) {
+ from_smtp = sieve_message_get_sender(nenv->msgctx);
+ if ( from_smtp == NULL ) {
+ /* "<>" */
+ i_zero(&env_from);
+ env_from.type = SIEVE_ADDRESS_SOURCE_EXPLICIT;
+ }
+ }
+ from = nact->from;
+ if ( (ret=sieve_address_source_get_address(&env_from, svinst,
+ senv, nenv->msgctx, nenv->flags, &from_smtp)) < 0 ) {
+ from_smtp = NULL;
+ } else if ( ret == 0 ) {
+ if ( mtctx->from_address != NULL )
+ from_smtp = mtctx->from_address;
+ else if ( svinst->user_email != NULL )
+ from_smtp = svinst->user_email;
+ else {
+ from_smtp = sieve_get_postmaster_smtp(senv);
+ if (from == NULL)
+ from = sieve_get_postmaster_address(senv);
+ }
+ }
+
+ /* Determine message from address */
+ if ( from == NULL ) {
+ if ( from_smtp == NULL )
+ from = sieve_get_postmaster_address(senv);
+ else {
+ from = t_strdup_printf("<%s>",
+ smtp_address_encode(from_smtp));
+ }
+ }
+
+ /* Determine subject */
+ if ( nact->message != NULL ) {
+ /* FIXME: handle UTF-8 */
+ subject = str_sanitize(nact->message, NTFY_MAILTO_MAX_SUBJECT);
+ } else if ( subject == NULL ) {
+ const char *const *hsubject;
+
+ /* Fetch subject from original message */
+ if ( mail_get_headers_utf8
+ (msgdata->mail, "subject", &hsubject) > 0 )
+ subject = str_sanitize(t_strdup_printf("Notification: %s", hsubject[0]),
+ NTFY_MAILTO_MAX_SUBJECT);
+ else
+ subject = "Notification: (no subject)";
+ }
+
+ /* Compose To and Cc headers */
+ to = NULL;
+ cc = NULL;
+ all = t_str_new(256);
+ for ( i = 0; i < count; i++ ) {
+ if ( recipients[i].carbon_copy ) {
+ if ( cc == NULL ) {
+ cc = t_str_new(256);
+ str_append(cc, recipients[i].full);
+ } else {
+ str_append(cc, ", ");
+ str_append(cc, recipients[i].full);
+ }
+ } else {
+ if ( to == NULL ) {
+ to = t_str_new(256);
+ str_append(to, recipients[i].full);
+ } else {
+ str_append(to, ", ");
+ str_append(to, recipients[i].full);
+ }
+ }
+ if ( i < 3) {
+ if ( i > 0 )
+ str_append(all, ", ");
+ str_append(all,
+ smtp_address_encode_path(recipients[i].address));
+ } else if (i == 3) {
+ str_printfa(all, ", ... (%u total)", count);
+ }
+ }
+
+ msg = t_str_new(512);
+ outmsgid = sieve_message_get_new_id(svinst);
+
+ rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(msg, "Message-ID", outmsgid);
+ rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+ rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+
+ rfc2822_header_write_address(msg, "From", from);
+
+ if ( to != NULL )
+ rfc2822_header_write_address(msg, "To", str_c(to));
+ if ( cc != NULL )
+ rfc2822_header_write_address(msg, "Cc", str_c(cc));
+
+ rfc2822_header_printf(msg, "Auto-Submitted",
+ "auto-notified; owner-email=\"%s\"",
+ smtp_address_encode(owner_email));
+ rfc2822_header_write(msg, "Precedence", "bulk");
+
+ /* Set importance */
+ switch ( nact->importance ) {
+ case 1:
+ rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
+ rfc2822_header_write(msg, "Importance", "High");
+ break;
+ case 3:
+ rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
+ rfc2822_header_write(msg, "Importance", "Low");
+ break;
+ case 2:
+ default:
+ rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
+ rfc2822_header_write(msg, "Importance", "Normal");
+ break;
+ }
+
+ /* Add custom headers */
+
+ headers = array_get(&mtctx->uri->headers, &hcount);
+ for ( h = 0; h < hcount; h++ ) {
+ const char *name = rfc2822_header_field_name_sanitize(headers[h].name);
+
+ rfc2822_header_write(msg, name, headers[h].body);
+ }
+
+ /* Generate message body */
+
+ rfc2822_header_write(msg, "MIME-Version", "1.0");
+ if ( body != NULL ) {
+ if (_contains_8bit(body)) {
+ rfc2822_header_write
+ (msg, "Content-Type", "text/plain; charset=utf-8");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+ } else {
+ rfc2822_header_write
+ (msg, "Content-Type", "text/plain; charset=us-ascii");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+ }
+ str_printfa(msg, "\r\n%s\r\n", body);
+
+ } else {
+ rfc2822_header_write
+ (msg, "Content-Type", "text/plain; charset=US-ASCII");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+
+ str_append(msg, "\r\nNotification of new message.\r\n");
+ }
+
+ sctx = sieve_smtp_start(senv, from_smtp);
+
+ /* Send message to all recipients */
+ for ( i = 0; i < count; i++ )
+ sieve_smtp_add_rcpt(sctx, recipients[i].address);
+
+ output = sieve_smtp_send(sctx);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ if ( (ret=sieve_smtp_finish(sctx, &error)) <= 0 ) {
+ if (ret < 0) {
+ sieve_enotify_global_error(nenv,
+ "failed to send mail notification to %s: %s (temporary failure)",
+ str_c(all), str_sanitize(error, 512));
+ } else {
+ sieve_enotify_global_log_error(nenv,
+ "failed to send mail notification to %s: %s (permanent failure)",
+ str_c(all), str_sanitize(error, 512));
+ }
+ } else {
+ struct event_passthrough *e =
+ sieve_enotify_create_finish_event(nenv)->
+ add_str("notify_target", str_c(all));
+
+ sieve_enotify_event_log(nenv, e->event(),
+ "sent mail notification to %s",
+ str_c(all));
+ }
+
+ return 0;
+}
+
+static int ntfy_mailto_action_execute
+(const struct sieve_enotify_exec_env *nenv,
+ const struct sieve_enotify_action *nact)
+{
+ struct sieve_instance *svinst = nenv->svinst;
+ const struct sieve_script_env *senv = nenv->scriptenv;
+ struct mail *mail = nenv->msgdata->mail;
+ const struct smtp_address *owner_email;
+ const char *const *hdsp;
+ int ret;
+
+ owner_email = svinst->user_email;
+ if ( owner_email == NULL &&
+ (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+ owner_email = sieve_message_get_final_recipient(nenv->msgctx);
+ if ( owner_email == NULL ) {
+ owner_email = sieve_get_postmaster_smtp(senv);
+ }
+ i_assert( owner_email != NULL );
+
+ /* Is the message an automatic reply ? */
+ if ( (ret=mail_get_headers(mail, "auto-submitted", &hdsp)) < 0 ) {
+ sieve_enotify_critical(nenv,
+ "mailto notification: "
+ "failed to read `auto-submitted' header field",
+ "mailto notification: "
+ "failed to read `auto-submitted' header field: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ return -1;
+ }
+
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if ( ret > 0 ) {
+ while ( *hdsp != NULL ) {
+ if ( strcasecmp(*hdsp, "no") != 0 ) {
+ const struct smtp_address *sender = NULL;
+ const char *from;
+
+ if ( (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 )
+ sender = sieve_message_get_sender(nenv->msgctx);
+ from = (sender == NULL ? "" : t_strdup_printf
+ (" from <%s>", smtp_address_encode(sender)));
+
+ sieve_enotify_global_info(nenv,
+ "not sending notification "
+ "for auto-submitted message%s", from);
+ return 0;
+ }
+ hdsp++;
+ }
+ }
+
+ T_BEGIN {
+ ret = ntfy_mailto_send(nenv, nact, owner_email);
+ } T_END;
+
+ return ret;
+}
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
new file mode 100644
index 0000000..c5a0953
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
@@ -0,0 +1,683 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
+ draft-duerst-mailto-bis-05.txt. Should fully migrate to new
+ specification when it matures. This requires modifications to the
+ address parser (no whitespace allowed within the address itself) and
+ UTF-8 support will be required in the URL.
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+
+#include "uri-mailto.h"
+
+/* Parser object */
+
+struct uri_mailto_parser {
+ pool_t pool;
+ const struct uri_mailto_log *log;
+
+ struct uri_mailto *uri;
+
+ const char **reserved_headers;
+ const char **unique_headers;
+
+ int max_recipients;
+ int max_headers;
+};
+
+/* Error handling */
+
+#define uri_mailto_error(PARSER, ...) \
+ uri_mailto_log((PARSER), LOG_TYPE_ERROR, \
+ __FILE__, __LINE__, __VA_ARGS__)
+#define uri_mailto_warning(PARSER, ...) \
+ uri_mailto_log((PARSER), LOG_TYPE_WARNING, \
+ __FILE__, __LINE__, __VA_ARGS__)
+
+static inline void ATTR_FORMAT(5, 6)
+uri_mailto_log(struct uri_mailto_parser *parser, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ if (parser->log == NULL || parser->log->logv == NULL)
+ return;
+
+ va_start(args, fmt);
+ parser->log->logv(parser->log->context, log_type,
+ csrc_filename, csrc_linenum, fmt, args);
+ va_end(args);
+}
+
+
+/*
+ * Error handling
+ */
+
+/*
+ * Reserved and unique headers
+ */
+
+static inline bool
+uri_mailto_header_is_reserved(struct uri_mailto_parser *parser,
+ const char *field_name)
+{
+ const char **hdr = parser->reserved_headers;
+
+ if (hdr == NULL)
+ return FALSE;
+
+ /* Check whether it is reserved */
+ while (*hdr != NULL) {
+ if (strcasecmp(field_name, *hdr) == 0)
+ return TRUE;
+ hdr++;
+ }
+ return FALSE;
+}
+
+static inline bool
+uri_mailto_header_is_unique(struct uri_mailto_parser *parser,
+ const char *field_name)
+{
+ const char **hdr = parser->unique_headers;
+
+ if (hdr == NULL)
+ return FALSE;
+
+ /* Check whether it is supposed to be unique */
+ while (*hdr != NULL) {
+ if (strcasecmp(field_name, *hdr) == 0)
+ return TRUE;
+ hdr++;
+ }
+ return FALSE;
+}
+
+/*
+ * Low-level URI parsing.
+ *
+ * FIXME: much of this implementation will be common to other URI schemes. This
+ * should be merged into a common implementation.
+ */
+
+static const char _qchar_lookup[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10
+ 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 20
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 30
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F0
+};
+
+static inline bool _is_qchar(char c)
+{
+ return ((_qchar_lookup[(unsigned char)c] & 0x01) != 0);
+}
+
+static inline int _decode_hex_digit(unsigned char digit)
+{
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return (int) digit - '0';
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ return (int) digit - 'a' + 0x0a;
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ return (int) digit - 'A' + 0x0A;
+ }
+ return -1;
+}
+
+static bool _parse_hex_value(const char **in, char *out)
+{
+ int value, digit;
+
+ if ((digit = _decode_hex_digit((unsigned char)**in)) < 0)
+ return FALSE;
+
+ value = digit << 4;
+ (*in)++;
+
+ if ((digit = _decode_hex_digit((unsigned char)**in)) < 0)
+ return FALSE;
+
+ value |= digit;
+ (*in)++;
+
+ if (value == 0)
+ return FALSE;
+
+ *out = (char)value;
+ return TRUE;
+}
+
+/*
+ * URI recipient parsing
+ */
+
+static bool
+uri_mailto_add_valid_recipient(struct uri_mailto_parser *parser,
+ string_t *recipient, bool cc)
+{
+ struct uri_mailto *uri = parser->uri;
+ struct uri_mailto_recipient *new_recipient;
+ struct uri_mailto_recipient *rcpts;
+ unsigned int count, i;
+ const char *error;
+ const struct smtp_address *address;
+
+ /* Verify recipient */
+ if ((address = sieve_address_parse_str(recipient, &error)) == NULL) {
+ uri_mailto_error(parser, "invalid recipient '%s': %s",
+ str_sanitize(str_c(recipient), 80), error);
+ return FALSE;
+ }
+
+ /* Add recipient to the uri */
+ if (uri != NULL) {
+ /* Get current recipients */
+ rcpts = array_get_modifiable(&uri->recipients, &count);
+
+ /* Enforce limits */
+ if (parser->max_recipients > 0 &&
+ (int)count >= parser->max_recipients) {
+ if ((int)count == parser->max_recipients) {
+ uri_mailto_warning(
+ parser,
+ "more than the maximum %u recipients specified; "
+ "rest is discarded",
+ parser->max_recipients);
+ }
+ return TRUE;
+ }
+
+ /* Check for duplicate first */
+ for (i = 0; i < count; i++) {
+ if (smtp_address_equals(rcpts[i].address, address)) {
+ /* Upgrade existing Cc: recipient to a To:
+ recipient if possible */
+ rcpts[i].carbon_copy =
+ (rcpts[i].carbon_copy && cc);
+
+ uri_mailto_warning(
+ parser,
+ "ignored duplicate recipient '%s'",
+ str_sanitize(str_c(recipient), 80));
+ return TRUE;
+ }
+ }
+
+ /* Add */
+ new_recipient = array_append_space(&uri->recipients);
+ new_recipient->carbon_copy = cc;
+ new_recipient->full = p_strdup(parser->pool, str_c(recipient));
+ new_recipient->address =
+ smtp_address_clone(parser->pool, address);
+ }
+
+ return TRUE;
+}
+
+static bool
+uri_mailto_parse_recipients(struct uri_mailto_parser *parser,
+ const char **uri_p)
+{
+ string_t *to = t_str_new(128);
+ const char *p = *uri_p;
+
+ if (*p == '\0' || *p == '?')
+ return TRUE;
+
+ while (*p != '\0' && *p != '?') {
+ if (*p == '%') {
+ /* % encoded character */
+ char ch;
+
+ p++;
+
+ /* Parse 2-digit hex value */
+ if (!_parse_hex_value(&p, &ch)) {
+ uri_mailto_error(parser, "invalid %% encoding");
+ return FALSE;
+ }
+
+ /* Check for delimiter */
+ if (ch == ',') {
+ /* Verify and add recipient */
+ if (!uri_mailto_add_valid_recipient(
+ parser, to, FALSE))
+ return FALSE;
+
+ /* Reset for next recipient */
+ str_truncate(to, 0);
+ } else {
+ /* Content character */
+ str_append_c(to, ch);
+ }
+ } else {
+ if (*p == ':' || *p == ';' || *p == ',' ||
+ !_is_qchar(*p)) {
+ uri_mailto_error(
+ parser,
+ "invalid character '%c' in 'to' part", *p);
+ return FALSE;
+ }
+
+ /* Content character */
+ str_append_c(to, *p);
+ p++;
+ }
+ }
+
+ i_assert(*p == '\0' || *p == '?');
+
+ /* Verify and add recipient */
+ if (!uri_mailto_add_valid_recipient(parser, to, FALSE))
+ return FALSE;
+
+ *uri_p = p;
+ return TRUE;
+}
+
+static bool
+uri_mailto_parse_header_recipients(struct uri_mailto_parser *parser,
+ string_t *rcpt_header, bool cc)
+{
+ string_t *to = t_str_new(128);
+ const char *p = (const char *)str_data(rcpt_header);
+ const char *pend = p + str_len(rcpt_header);
+
+ while (p < pend) {
+ if (*p == ',') {
+ /* Verify and add recipient */
+ if (!uri_mailto_add_valid_recipient(parser, to, cc))
+ return FALSE;
+
+ /* Reset for next recipient */
+ str_truncate(to, 0);
+ } else {
+ /* Content character */
+ str_append_c(to, *p);
+ }
+ p++;
+ }
+
+ /* Verify and add recipient */
+ if (!uri_mailto_add_valid_recipient(parser, to, cc))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* URI header parsing */
+
+static bool
+uri_mailto_header_is_duplicate(struct uri_mailto_parser *parser,
+ const char *field_name)
+{
+ struct uri_mailto *uri = parser->uri;
+
+ if (uri == NULL)
+ return FALSE;
+
+ if (uri_mailto_header_is_unique(parser, field_name)) {
+ const struct uri_mailto_header_field *hdrs;
+ unsigned int count, i;
+
+ hdrs = array_get(&uri->headers, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(hdrs[i].name, field_name) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+uri_mailto_parse_headers(struct uri_mailto_parser *parser, const char **uri_p)
+{
+ struct uri_mailto *uri = parser->uri;
+ unsigned int header_count = 0;
+ string_t *field = t_str_new(128);
+ const char *p = *uri_p;
+
+ while (*p != '\0') {
+ enum {
+ _HNAME_IGNORED,
+ _HNAME_GENERIC,
+ _HNAME_TO,
+ _HNAME_CC,
+ _HNAME_SUBJECT,
+ _HNAME_BODY,
+ } hname_type = _HNAME_GENERIC;
+ struct uri_mailto_header_field *hdrf = NULL;
+ const char *field_name;
+
+ /* Parse field name */
+ while (*p != '\0' && *p != '=') {
+ char ch = *p;
+ p++;
+
+ if (ch == '%') {
+ /* Encoded, parse 2-digit hex value */
+ if (!_parse_hex_value(&p, &ch)) {
+ uri_mailto_error(parser,
+ "invalid %% encoding");
+ return FALSE;
+ }
+ } else if (ch != '=' && !_is_qchar(ch)) {
+ uri_mailto_error(
+ parser,
+ "invalid character '%c' in header field name part",
+ ch);
+ return FALSE;
+ }
+
+ str_append_c(field, ch);
+ }
+ if (*p != '\0')
+ p++;
+
+ /* Verify field name */
+ if (!rfc2822_header_field_name_verify(str_c(field),
+ str_len(field))) {
+ uri_mailto_error(parser, "invalid header field name");
+ return FALSE;
+ }
+
+ if (parser->max_headers > -1 &&
+ (int)header_count >= parser->max_headers) {
+ /* Refuse to accept more headers than allowed by policy
+ */
+ if ((int)header_count == parser->max_headers) {
+ uri_mailto_warning(
+ parser,
+ "more than the maximum %u headers specified; "
+ "rest is discarded",
+ parser->max_headers);
+ }
+
+ hname_type = _HNAME_IGNORED;
+ } else {
+ /* Add new header field to array and assign its name */
+
+ field_name = str_c(field);
+ if (strcasecmp(field_name, "to") == 0)
+ hname_type = _HNAME_TO;
+ else if (strcasecmp(field_name, "cc") == 0)
+ hname_type = _HNAME_CC;
+ else if (strcasecmp(field_name, "subject") == 0)
+ hname_type = _HNAME_SUBJECT;
+ else if (strcasecmp(field_name, "body") == 0)
+ hname_type = _HNAME_BODY;
+ else if (!uri_mailto_header_is_reserved(parser, field_name)) {
+ if (uri != NULL) {
+ if (!uri_mailto_header_is_duplicate(parser, field_name)) {
+ hdrf = array_append_space(&uri->headers);
+ hdrf->name = p_strdup(parser->pool, field_name);
+ } else {
+ uri_mailto_warning(
+ parser,
+ "ignored duplicate for unique header field '%s'",
+ str_sanitize(field_name, 32));
+ hname_type = _HNAME_IGNORED;
+ }
+ } else {
+ hname_type = _HNAME_IGNORED;
+ }
+ } else {
+ uri_mailto_warning(
+ parser,
+ "ignored reserved header field '%s'",
+ str_sanitize(field_name, 32));
+ hname_type = _HNAME_IGNORED;
+ }
+ }
+
+ header_count++;
+
+ /* Reset for field body */
+ str_truncate(field, 0);
+
+ /* Parse field body */
+ while (*p != '\0' && *p != '&') {
+ char ch = *p;
+ p++;
+
+ if (ch == '%') {
+ /* Encoded, parse 2-digit hex value */
+ if (!_parse_hex_value(&p, &ch)) {
+ uri_mailto_error(parser,
+ "invalid %% encoding");
+ return FALSE;
+ }
+ } else if (ch != '=' && !_is_qchar(ch)) {
+ uri_mailto_error(
+ parser,
+ "invalid character '%c' in header field value part",
+ ch);
+ return FALSE;
+ }
+ str_append_c(field, ch);
+ }
+ if (*p != '\0')
+ p++;
+
+ /* Verify field body */
+ if (hname_type == _HNAME_BODY) {
+ // FIXME: verify body ...
+ } else {
+ if (!rfc2822_header_field_body_verify(
+ str_c(field), str_len(field), FALSE, FALSE)) {
+ uri_mailto_error(parser,
+ "invalid header field body");
+ return FALSE;
+ }
+ }
+
+ /* Assign field body */
+
+ switch (hname_type) {
+ case _HNAME_IGNORED:
+ break;
+ case _HNAME_TO:
+ /* Gracefully allow duplicate To fields */
+ if (!uri_mailto_parse_header_recipients(
+ parser, field, FALSE))
+ return FALSE;
+ break;
+ case _HNAME_CC:
+ /* Gracefully allow duplicate Cc fields */
+ if (!uri_mailto_parse_header_recipients(
+ parser, field, TRUE))
+ return FALSE;
+ break;
+ case _HNAME_SUBJECT:
+ /* Ignore duplicate subject field */
+ if (uri != NULL) {
+ if (uri->subject == NULL) {
+ uri->subject =
+ p_strdup(parser->pool, str_c(field));
+ } else {
+ uri_mailto_warning(
+ parser,
+ "ignored duplicate subject field");
+ }
+ }
+ break;
+ case _HNAME_BODY:
+ /* Ignore duplicate body field */
+ if (uri != NULL) {
+ if (uri->body == NULL) {
+ uri->body = p_strdup(
+ parser->pool, str_c(field));
+ } else {
+ uri_mailto_warning(
+ parser, "ignored duplicate body field");
+ }
+ }
+ break;
+ case _HNAME_GENERIC:
+ if (uri != NULL && hdrf != NULL)
+ hdrf->body = p_strdup(parser->pool, str_c(field));
+ break;
+ }
+
+ /* Reset for next name */
+ str_truncate(field, 0);
+ }
+
+ /* Skip '&' */
+ if (*p != '\0')
+ p++;
+
+ *uri_p = p;
+ return TRUE;
+}
+
+static bool
+uri_mailto_parse_uri(struct uri_mailto_parser *parser, const char *uri_body)
+{
+ const char *p = uri_body;
+
+ /*
+ * mailtoURI = "mailto:" [ to ] [ hfields ]
+ * to = [ addr-spec *("%2C" addr-spec ) ]
+ * hfields = "?" hfield *( "&" hfield )
+ * hfield = hfname "=" hfvalue
+ * hfname = *qchar
+ * hfvalue = *qchar
+ * addr-spec = local-part "@" domain
+ * local-part = dot-atom / quoted-string
+ * qchar = unreserved / pct-encoded / some-delims
+ * some-delims = "!" / "$" / "'" / "(" / ")" / "*"
+ * / "+" / "," / ";" / ":" / "@"
+ *
+ * to ~= *tqchar
+ * tqchar ~= <qchar> without ";" and ":"
+ *
+ * Scheme 'mailto:' already parsed, starting parse after colon
+ */
+
+ /* First extract to-part by searching for '?' and decoding % items
+ */
+
+ if (!uri_mailto_parse_recipients(parser, &p))
+ return FALSE;
+
+ if (*p == '\0')
+ return TRUE;
+ i_assert(*p == '?');
+ p++;
+
+ /* Extract hfield items */
+
+ while (*p != '\0') {
+ /* Extract hfield item by searching for '&' and decoding '%'
+ items */
+ if (!uri_mailto_parse_headers(parser, &p))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+bool uri_mailto_validate(const char *uri_body,
+ const char **reserved_headers,
+ const char **unique_headers, int max_recipients,
+ int max_headers, const struct uri_mailto_log *log)
+{
+ struct uri_mailto_parser parser;
+
+ i_zero(&parser);
+ parser.log = log;
+ parser.max_recipients = max_recipients;
+ parser.max_headers = max_headers;
+ parser.reserved_headers = reserved_headers;
+ parser.unique_headers = unique_headers;
+
+ /* If no errors are reported, we don't need to record any data */
+ if (log != NULL) {
+ parser.pool = pool_datastack_create();
+
+ parser.uri = p_new(parser.pool, struct uri_mailto, 1);
+ p_array_init(&parser.uri->recipients, parser.pool, max_recipients);
+ p_array_init(&parser.uri->headers, parser.pool, max_headers);
+ }
+
+ if (!uri_mailto_parse_uri(&parser, uri_body))
+ return FALSE;
+
+ if (log != NULL) {
+ if (array_count(&parser.uri->recipients) == 0) {
+ uri_mailto_warning(
+ &parser,
+ "notification URI specifies no recipients");
+ }
+ }
+ return TRUE;
+}
+
+/*
+ * Parsing
+ */
+
+struct uri_mailto *
+uri_mailto_parse(const char *uri_body, pool_t pool,
+ const char **reserved_headers, const char **unique_headers,
+ int max_recipients, int max_headers,
+ const struct uri_mailto_log *log)
+{
+ struct uri_mailto_parser parser;
+
+ parser.pool = pool;
+ parser.log = log;
+ parser.max_recipients = max_recipients;
+ parser.max_headers = max_headers;
+ parser.reserved_headers = reserved_headers;
+ parser.unique_headers = unique_headers;
+
+ parser.uri = p_new(pool, struct uri_mailto, 1);
+ p_array_init(&parser.uri->recipients, pool, max_recipients);
+ p_array_init(&parser.uri->headers, pool, max_headers);
+
+ if (!uri_mailto_parse_uri(&parser, uri_body))
+ return NULL;
+
+ if (log != NULL) {
+ if (array_count(&parser.uri->recipients) == 0) {
+ uri_mailto_warning(
+ &parser,
+ "notification URI specifies no recipients");
+ }
+ }
+ return parser.uri;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.h b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.h
new file mode 100644
index 0000000..8e9e057
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.h
@@ -0,0 +1,49 @@
+#ifndef URI_MAILTO_H
+#define URI_MAILTO_H
+
+/*
+ * Types
+ */
+
+struct uri_mailto_header_field {
+ const char *name;
+ const char *body;
+};
+
+struct uri_mailto_recipient {
+ const char *full;
+ const struct smtp_address *address;
+ bool carbon_copy;
+};
+
+ARRAY_DEFINE_TYPE(recipients, struct uri_mailto_recipient);
+ARRAY_DEFINE_TYPE(headers, struct uri_mailto_header_field);
+
+struct uri_mailto_log {
+ void *context;
+
+ void (*logv)(void *context, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+};
+
+struct uri_mailto {
+ ARRAY_TYPE(recipients) recipients;
+ ARRAY_TYPE(headers) headers;
+ const char *subject;
+ const char *body;
+};
+
+bool uri_mailto_validate
+ (const char *uri_body, const char **reserved_headers,
+ const char **unique_headers, int max_recipients, int max_headers,
+ const struct uri_mailto_log *log);
+
+struct uri_mailto *uri_mailto_parse
+(const char *uri_body, pool_t pool, const char **reserved_headers,
+ const char **unique_headers, int max_recipients, int max_headers,
+ const struct uri_mailto_log *log);
+
+#endif
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h b/pigeonhole/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h
new file mode 100644
index 0000000..fd8b574
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/sieve-ext-enotify.h
@@ -0,0 +1,197 @@
+#ifndef SIEVE_EXT_ENOTIFY_H
+#define SIEVE_EXT_ENOTIFY_H
+
+#include "lib.h"
+#include "compat.h"
+#include <stdarg.h>
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+/*
+ * Forward declarations
+ */
+
+struct sieve_enotify_method;
+struct sieve_enotify_env;
+struct sieve_enotify_action;
+struct sieve_enotify_print_env;
+struct sieve_enotify_exec_env;
+
+/*
+ * Notify method definition
+ */
+
+struct sieve_enotify_method_def {
+ const char *identifier;
+
+ /* Registration */
+ bool (*load)
+ (const struct sieve_enotify_method *nmth, void **context);
+ void (*unload)
+ (const struct sieve_enotify_method *nmth);
+
+ /* Validation */
+ bool (*compile_check_uri)
+ (const struct sieve_enotify_env *nenv, const char *uri,
+ const char *uri_body);
+ bool (*compile_check_message)
+ (const struct sieve_enotify_env *nenv, string_t *message);
+ bool (*compile_check_from)
+ (const struct sieve_enotify_env *nenv, string_t *from);
+ bool (*compile_check_option)
+ (const struct sieve_enotify_env *nenv, const char *option,
+ const char *value);
+
+ /* Runtime */
+ bool (*runtime_check_uri)
+ (const struct sieve_enotify_env *nenv, const char *uri,
+ const char *uri_body);
+ const char *(*runtime_get_method_capability)
+ (const struct sieve_enotify_env *nenv, const char *uri,
+ const char *uri_body, const char *capability);
+ bool (*runtime_check_operands)
+ (const struct sieve_enotify_env *nenv, const char *uri,
+ const char *uri_body, string_t *message, string_t *from,
+ pool_t context_pool, void **method_context);
+ bool (*runtime_set_option)
+ (const struct sieve_enotify_env *nenv, void *method_context,
+ const char *option, const char *value);
+
+ /* Action duplicates */
+ int (*action_check_duplicates)
+ (const struct sieve_enotify_env *nenv,
+ const struct sieve_enotify_action *nact,
+ const struct sieve_enotify_action *nact_other);
+
+ /* Action print */
+ void (*action_print)
+ (const struct sieve_enotify_print_env *penv,
+ const struct sieve_enotify_action *nact);
+
+ /* Action execution
+ (returns 0 if all is ok and -1 for temporary error)
+ */
+ int (*action_execute)
+ (const struct sieve_enotify_exec_env *nenv,
+ const struct sieve_enotify_action *nact);
+};
+
+/*
+ * Notify method instance
+ */
+
+struct sieve_enotify_method {
+ const struct sieve_enotify_method_def *def;
+ int id;
+
+ struct sieve_instance *svinst;
+ void *context;
+};
+
+const struct sieve_enotify_method *sieve_enotify_method_register
+ (struct sieve_instance *svinst,
+ const struct sieve_enotify_method_def *nmth_def);
+void sieve_enotify_method_unregister
+ (const struct sieve_enotify_method *nmth);
+
+/*
+ * Notify method environment
+ */
+
+struct sieve_enotify_env {
+ struct sieve_instance *svinst;
+
+ const struct sieve_enotify_method *method;
+
+ struct sieve_error_handler *ehandler;
+ const char *location;
+ struct event *event;
+};
+
+/*
+ * Notify method printing
+ */
+
+void sieve_enotify_method_printf
+ (const struct sieve_enotify_print_env *penv, const char *fmt, ...)
+ ATTR_FORMAT(2, 3);
+
+/*
+ * Notify execution environment
+ */
+
+struct sieve_enotify_exec_env {
+ struct sieve_instance *svinst;
+ enum sieve_execute_flags flags;
+
+ const struct sieve_enotify_method *method;
+
+ const struct sieve_script_env *scriptenv;
+ const struct sieve_message_data *msgdata;
+ struct sieve_message_context *msgctx;
+
+ struct sieve_error_handler *ehandler;
+ const char *location;
+ struct event *event;
+};
+
+struct event_passthrough *
+sieve_enotify_create_finish_event(const struct sieve_enotify_exec_env *nenv);
+
+/*
+ * Notify action
+ */
+
+struct sieve_enotify_action {
+ const struct sieve_enotify_method *method;
+ void *method_context;
+
+ sieve_number_t importance;
+ const char *message;
+ const char *from;
+};
+
+/*
+ * Error handling
+ */
+
+#define sieve_enotify_error(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_ERROR, (ENV)->location, 0, __VA_ARGS__ )
+#define sieve_enotify_warning(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_WARNING, \
+ (ENV)->location, 0, __VA_ARGS__ )
+#define sieve_enotify_info(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_INFO, \
+ (ENV)->location, 0, __VA_ARGS__ )
+#define sieve_enotify_critical(ENV, ...) \
+ sieve_critical((ENV)->svinst, (ENV)->ehandler, NULL, __VA_ARGS__ )
+
+#define sieve_enotify_global_error(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_ERROR, (ENV)->location, \
+ SIEVE_ERROR_FLAG_GLOBAL, __VA_ARGS__ )
+#define sieve_enotify_global_warning(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_WARNING, (ENV)->location, \
+ SIEVE_ERROR_FLAG_GLOBAL, __VA_ARGS__ )
+#define sieve_enotify_global_info(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_INFO, (ENV)->location, \
+ SIEVE_ERROR_FLAG_GLOBAL, __VA_ARGS__ )
+
+#define sieve_enotify_event_log(ENV, EVENT, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (EVENT), \
+ LOG_TYPE_INFO, (ENV)->location, \
+ SIEVE_ERROR_FLAG_GLOBAL, __VA_ARGS__ )
+
+#define sieve_enotify_global_log_error(ENV, ...) \
+ sieve_event_log((ENV)->svinst, (ENV)->ehandler, (ENV)->event, \
+ LOG_TYPE_ERROR, (ENV)->location, \
+ (SIEVE_ERROR_FLAG_GLOBAL | \
+ SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO), __VA_ARGS__ )
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c b/pigeonhole/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
new file mode 100644
index 0000000..8a6c071
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/tst-notify-method-capability.c
@@ -0,0 +1,233 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * String test
+ *
+ * Syntax:
+ * notify_method_capability [COMPARATOR] [MATCH-TYPE]
+ * <notification-uri: string>
+ * <notification-capability: string>
+ * <key-list: string-list>
+ */
+
+static bool tst_notifymc_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_notifymc_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_notifymc_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def notify_method_capability_test = {
+ .identifier = "notify_method_capability",
+ .type = SCT_TEST,
+ .positional_args = 3,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_notifymc_registered,
+ .validate = tst_notifymc_validate,
+ .generate = tst_notifymc_generate
+};
+
+/*
+ * String operation
+ */
+
+static bool tst_notifymc_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_notifymc_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def notify_method_capability_operation = {
+ .mnemonic = "NOTIFY_METHOD_CAPABILITY",
+ .ext_def = &enotify_extension,
+ .code = EXT_ENOTIFY_OPERATION_NOTIFY_METHOD_CAPABILITY,
+ .dump = tst_notifymc_operation_dump,
+ .execute = tst_notifymc_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_notifymc_optional {
+ OPT_END,
+ OPT_COMPARATOR,
+ OPT_MATCH_TYPE
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_notifymc_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, OPT_MATCH_TYPE);
+
+ return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_notifymc_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "notification-uri", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "notification-capability", 2, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key-list", 3, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_notifymc_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit
+ (cgenv->sblock, cmd->ext, &notify_method_capability_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_notifymc_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "NOTIFY_METHOD_CAPABILITY");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return
+ sieve_opr_string_dump(denv, address, "notify uri") &&
+ sieve_opr_string_dump(denv, address, "notify capability") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_notifymc_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ string_t *notify_uri, *notify_capability;
+ struct sieve_stringlist *value_list, *key_list;
+ const char *cap_value;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Handle match-type and comparator operands */
+ if ( sieve_match_opr_optional_read
+ (renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+ return ret;
+
+ /* Read notify uri */
+ if ( (ret=sieve_opr_string_read(renv, address, "notify-uri", &notify_uri))
+ <= 0 )
+ return ret;
+
+ /* Read notify capability */
+ if ( (ret=sieve_opr_string_read
+ (renv, address, "notify-capability", &notify_capability)) <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "notify_method_capability test");
+
+ cap_value = ext_enotify_runtime_get_method_capability
+ (renv, notify_uri, str_c(notify_capability));
+
+ if ( cap_value != NULL ) {
+ value_list = sieve_single_stringlist_create_cstr(renv, cap_value, TRUE);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret))
+ < 0 )
+ return ret;
+ } else {
+ match = 0;
+ }
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c b/pigeonhole/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
new file mode 100644
index 0000000..becc41c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/tst-valid-notify-method.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Valid_notify_method test
+ *
+ * Syntax:
+ * valid_notify_method <notification-uris: string-list>
+ */
+
+static bool tst_vnotifym_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_vnotifym_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def valid_notify_method_test = {
+ .identifier = "valid_notify_method",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_vnotifym_validate,
+ .generate = tst_vnotifym_generate
+};
+
+/*
+ * Valid_notify_method operation
+ */
+
+static bool tst_vnotifym_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_vnotifym_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def valid_notify_method_operation = {
+ .mnemonic = "VALID_NOTIFY_METHOD",
+ .ext_def = &enotify_extension,
+ .code = EXT_ENOTIFY_OPERATION_VALID_NOTIFY_METHOD,
+ .dump = tst_vnotifym_operation_dump,
+ .execute = tst_vnotifym_operation_execute
+};
+
+/*
+ * Test validation
+ */
+
+static bool tst_vnotifym_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "notification-uris", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_vnotifym_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &valid_notify_method_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_vnotifym_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "VALID_NOTIFY_METHOD");
+ sieve_code_descend(denv);
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "notify-uris");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_vnotifym_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_stringlist *notify_uris;
+ string_t *uri_item;
+ bool all_valid = TRUE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read notify uris */
+ if ( (ret=sieve_opr_stringlist_read
+ (renv, address, "notify-uris", &notify_uris)) <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "valid_notify_method test");
+
+ uri_item = NULL;
+ while ( (ret=sieve_stringlist_next_item(notify_uris, &uri_item)) > 0 ) {
+ if ( !ext_enotify_runtime_method_validate(renv, uri_item) ) {
+ all_valid = FALSE;
+ break;
+ }
+ }
+
+ if ( ret < 0 ) {
+ sieve_runtime_trace_error(renv, "invalid method uri item");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ sieve_interpreter_set_test_result(renv->interp, all_valid);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/enotify/vmodf-encodeurl.c b/pigeonhole/src/lib-sieve/plugins/enotify/vmodf-encodeurl.c
new file mode 100644
index 0000000..801f882
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/enotify/vmodf-encodeurl.c
@@ -0,0 +1,119 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "unichar.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-enotify-common.h"
+
+/*
+ * Encodeurl modifier
+ */
+
+static bool
+mod_encodeurl_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+
+const struct sieve_variables_modifier_def encodeurl_modifier = {
+ SIEVE_OBJECT("encodeurl", &encodeurl_operand, 0),
+ 15,
+ mod_encodeurl_modify
+};
+
+/*
+ * Modifier operand
+ */
+
+static const struct sieve_extension_objects ext_enotify_modifiers =
+ SIEVE_VARIABLES_DEFINE_MODIFIER(encodeurl_modifier);
+
+const struct sieve_operand_def encodeurl_operand = {
+ .name = "modifier",
+ .ext_def = &enotify_extension,
+ .class = &sieve_variables_modifier_operand_class,
+ .interface = &ext_enotify_modifiers
+};
+
+/*
+ * Modifier implementation
+ */
+
+static const char _uri_reserved_lookup[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 00
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, // 20
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, // 30
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, // 50
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, // 70
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // C0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // D0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // E0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // F0
+};
+
+static bool
+mod_encodeurl_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result)
+{
+ size_t max_var_size =
+ sieve_variables_get_max_variable_size(modf->var_ext);
+ const unsigned char *p, *poff, *pend;
+ size_t new_size;
+
+ if ( str_len(in) == 0 ) {
+ *result = in;
+ return TRUE;
+ }
+
+ /* allocate new string */
+ new_size = str_len(in) + 32;
+ if (new_size > max_var_size)
+ new_size = max_var_size;
+ *result = t_str_new(new_size + 1);
+
+ /* escape string */
+ p = str_data(in);
+ pend = p + str_len(in);
+ poff = p;
+ while (p < pend) {
+ unsigned int i, n = uni_utf8_char_bytes(*p);
+
+ if (n > 1 || (_uri_reserved_lookup[*p] & 0x01) != 0) {
+ str_append_data(*result, poff, p - poff);
+ poff = p;
+
+ if (str_len(*result) + 3 * n > max_var_size)
+ break;
+
+ str_printfa(*result, "%%%02X", *p);
+ for (i = 1; i < n && p < pend; i++) {
+ p++;
+ poff++;
+ str_printfa(*result, "%%%02X", *p);
+ }
+
+ poff++;
+ } else if ((str_len(*result) + (p - poff) + 1) > max_var_size) {
+ break;
+ }
+ p++;
+ }
+
+ str_append_data(*result, poff, p - poff);
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/Makefile.am b/pigeonhole/src/lib-sieve/plugins/environment/Makefile.am
new file mode 100644
index 0000000..916362e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_environment.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-environment.c
+
+libsieve_ext_environment_la_SOURCES = \
+ $(tests) \
+ ext-environment-common.c \
+ ext-environment.c
+
+public_headers = \
+ sieve-ext-environment.h
+
+headers = \
+ ext-environment-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in b/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in
new file mode 100644
index 0000000..68424f4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in
@@ -0,0 +1,753 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/environment
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_environment_la_LIBADD =
+am__objects_1 = tst-environment.lo
+am_libsieve_ext_environment_la_OBJECTS = $(am__objects_1) \
+ ext-environment-common.lo ext-environment.lo
+libsieve_ext_environment_la_OBJECTS = \
+ $(am_libsieve_ext_environment_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-environment-common.Plo \
+ ./$(DEPDIR)/ext-environment.Plo \
+ ./$(DEPDIR)/tst-environment.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_environment_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_environment_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_environment.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-environment.c
+
+libsieve_ext_environment_la_SOURCES = \
+ $(tests) \
+ ext-environment-common.c \
+ ext-environment.c
+
+public_headers = \
+ sieve-ext-environment.h
+
+headers = \
+ ext-environment-common.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/environment/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/environment/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_environment.la: $(libsieve_ext_environment_la_OBJECTS) $(libsieve_ext_environment_la_DEPENDENCIES) $(EXTRA_libsieve_ext_environment_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_environment_la_OBJECTS) $(libsieve_ext_environment_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-environment-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-environment.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-environment.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-environment-common.Plo
+ -rm -f ./$(DEPDIR)/ext-environment.Plo
+ -rm -f ./$(DEPDIR)/tst-environment.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-environment-common.Plo
+ -rm -f ./$(DEPDIR)/ext-environment.Plo
+ -rm -f ./$(DEPDIR)/tst-environment.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.c b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.c
new file mode 100644
index 0000000..28d4251
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.c
@@ -0,0 +1,339 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-interpreter.h"
+
+#include "ext-environment-common.h"
+
+struct ext_environment_interpreter_context;
+
+/*
+ * Core environment items
+ */
+
+static const struct sieve_environment_item *core_env_items[] = {
+ &domain_env_item,
+ &host_env_item,
+ &location_env_item,
+ &phase_env_item,
+ &name_env_item,
+ &version_env_item
+};
+
+static unsigned int core_env_items_count = N_ELEMENTS(core_env_items);
+
+static void
+sieve_environment_item_insert(struct ext_environment_interpreter_context *ctx,
+ const struct sieve_environment_item *item);
+
+/*
+ * Validator context
+ */
+
+struct ext_environment_interpreter_context {
+ HASH_TABLE(const char *,
+ const struct sieve_environment_item *) name_items;
+ ARRAY(const struct sieve_environment_item *) prefix_items;
+
+ bool active:1;
+};
+
+static void
+ext_environment_interpreter_extension_free(const struct sieve_extension *ext,
+ struct sieve_interpreter *interp,
+ void *context);
+
+struct sieve_interpreter_extension environment_interpreter_extension = {
+ .ext_def = &environment_extension,
+ .free = ext_environment_interpreter_extension_free,
+};
+
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_create(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ pool_t pool = sieve_interpreter_pool(interp);
+ struct ext_environment_interpreter_context *ctx;
+
+ ctx = p_new(pool, struct ext_environment_interpreter_context, 1);
+
+ hash_table_create(&ctx->name_items, default_pool, 0, str_hash, strcmp);
+ i_array_init(&ctx->prefix_items, 16);
+
+ sieve_interpreter_extension_register(interp, this_ext,
+ &environment_interpreter_extension,
+ (void *)ctx);
+ return ctx;
+}
+
+static void
+ext_environment_interpreter_extension_free(
+ const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_interpreter *interp ATTR_UNUSED, void *context)
+{
+ struct ext_environment_interpreter_context *ctx =
+ (struct ext_environment_interpreter_context *)context;
+
+ hash_table_destroy(&ctx->name_items);
+ array_free(&ctx->prefix_items);
+}
+
+static struct ext_environment_interpreter_context *
+ext_environment_interpreter_context_get(const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_environment_interpreter_context *ctx =
+ (struct ext_environment_interpreter_context *)
+ sieve_interpreter_extension_get_context(interp, this_ext);
+
+ if (ctx == NULL) {
+ ctx = ext_environment_interpreter_context_create(
+ this_ext, interp);
+ }
+ return ctx;
+}
+
+void ext_environment_interpreter_init(const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_environment_interpreter_context *ctx;
+ unsigned int i;
+
+ /* Create our context */
+ ctx = ext_environment_interpreter_context_get(this_ext, interp);
+
+ for (i = 0; i < core_env_items_count; i++)
+ sieve_environment_item_insert(ctx, core_env_items[i]);
+
+ ctx->active = TRUE;
+}
+
+bool sieve_ext_environment_is_active(const struct sieve_extension *env_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_environment_interpreter_context *ctx =
+ ext_environment_interpreter_context_get(env_ext, interp);
+
+ return (ctx != NULL && ctx->active);
+}
+
+/*
+ * Registration
+ */
+
+static void
+sieve_environment_item_insert(struct ext_environment_interpreter_context *ctx,
+ const struct sieve_environment_item *item)
+{
+ if (!item->prefix)
+ hash_table_insert(ctx->name_items, item->name, item);
+ else
+ array_append(&ctx->prefix_items, &item, 1);
+}
+
+void sieve_environment_item_register(const struct sieve_extension *env_ext,
+ struct sieve_interpreter *interp,
+ const struct sieve_environment_item *item)
+{
+ struct ext_environment_interpreter_context *ctx;
+
+ i_assert(sieve_extension_is(env_ext, environment_extension));
+ ctx = ext_environment_interpreter_context_get(env_ext, interp);
+
+ sieve_environment_item_insert(ctx, item);
+}
+
+/*
+ * Retrieval
+ */
+
+static const struct sieve_environment_item *
+ext_environment_item_lookup(struct ext_environment_interpreter_context *ctx,
+ const char **_name)
+{
+ const struct sieve_environment_item *item;
+ const char *name = *_name;
+
+ item = hash_table_lookup(ctx->name_items, name);
+ if (item != NULL)
+ return item;
+
+ array_foreach_elem(&ctx->prefix_items, item) {
+ size_t prefix_len;
+
+ i_assert(item->prefix);
+ prefix_len = strlen(item->name);
+
+ if (str_begins(name, item->name)) {
+ if (name[prefix_len] == '.') {
+ *_name = &name[prefix_len+1];
+ return item;
+ } else if (name[prefix_len] == '\0') {
+ *_name = &name[prefix_len+1];
+ return item;
+ }
+ }
+ }
+ return NULL;
+}
+
+const char *
+ext_environment_item_get_value(const struct sieve_extension *env_ext,
+ const struct sieve_runtime_env *renv,
+ const char *name)
+{
+ struct ext_environment_interpreter_context *ctx;
+ const struct sieve_environment_item *item;
+
+ i_assert(sieve_extension_is(env_ext, environment_extension));
+ ctx = ext_environment_interpreter_context_get(env_ext, renv->interp);
+
+ item = ext_environment_item_lookup(ctx, &name);
+ if (item == NULL)
+ return NULL;
+
+ if (item->value != NULL)
+ return item->value;
+ if (item->get_value != NULL)
+ return item->get_value(renv, name);
+ return NULL;
+}
+
+/*
+ * Default environment items
+ */
+
+/* "domain":
+
+ The primary DNS domain associated with the Sieve execution context, usually
+ but not always a proper suffix of the host name.
+ */
+
+static const char *
+envit_domain_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return eenv->svinst->domainname;
+}
+
+const struct sieve_environment_item domain_env_item = {
+ .name = "domain",
+ .get_value = envit_domain_get_value,
+};
+
+/* "host":
+
+ The fully-qualified domain name of the host where the Sieve script is
+ executing.
+ */
+
+static const char *
+envit_host_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return eenv->svinst->hostname;
+}
+
+const struct sieve_environment_item host_env_item = {
+ .name = "host",
+ .get_value = envit_host_get_value,
+};
+
+/* "location":
+
+ Sieve evaluation can be performed at various different points as messages
+ are processed. This item provides additional information about the type of
+ service that is evaluating the script. Possible values are:
+ "MTA" - the Sieve script is being evaluated by a Message Transfer Agent
+ "MDA" - evaluation is being performed by a Mail Delivery Agent
+ "MUA" - evaluation is being performed by a Mail User Agent (right...)
+ "MS" - evaluation is being performed by a Message Store
+ */
+
+static const char *
+envit_location_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ switch (eenv->svinst->env_location ) {
+ case SIEVE_ENV_LOCATION_MDA:
+ return "MDA";
+ case SIEVE_ENV_LOCATION_MTA:
+ return "MTA";
+ case SIEVE_ENV_LOCATION_MS:
+ return "MS";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+const struct sieve_environment_item location_env_item = {
+ .name = "location",
+ .get_value = envit_location_get_value
+};
+
+/* "phase":
+
+ The point relative to final delivery where the Sieve script is being
+ evaluated. Possible values are "pre", "during", and "post", referring
+ respectively to processing before, during, and after final delivery has
+ taken place.
+ */
+
+static const char *
+envit_phase_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ switch (eenv->svinst->delivery_phase) {
+ case SIEVE_DELIVERY_PHASE_PRE:
+ return "pre";
+ case SIEVE_DELIVERY_PHASE_DURING:
+ return "during";
+ case SIEVE_DELIVERY_PHASE_POST:
+ return "post";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+const struct sieve_environment_item phase_env_item = {
+ .name = "phase",
+ .get_value = envit_phase_get_value
+};
+
+/* "name":
+
+ The product name associated with the Sieve interpreter.
+ */
+
+const struct sieve_environment_item name_env_item = {
+ .name = "name",
+ .value = PIGEONHOLE_NAME" Sieve"
+};
+
+/* "version":
+
+ The product version associated with the Sieve interpreter. The meaning of the
+ product version string is product-specific and should always be considered
+ in the context of the product name given by the "name" item.
+ */
+
+const struct sieve_environment_item version_env_item = {
+ .name = "version",
+ .value = PIGEONHOLE_VERSION,
+};
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.h b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.h
new file mode 100644
index 0000000..288b82e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment-common.h
@@ -0,0 +1,53 @@
+#ifndef EXT_ENVIRONMENT_COMMON_H
+#define EXT_ENVIRONMENT_COMMON_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ext-environment.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def environment_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def tst_environment;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def tst_environment_operation;
+
+/*
+ * Environment items
+ */
+
+extern const struct sieve_environment_item domain_env_item;
+extern const struct sieve_environment_item host_env_item;
+extern const struct sieve_environment_item location_env_item;
+extern const struct sieve_environment_item phase_env_item;
+extern const struct sieve_environment_item name_env_item;
+extern const struct sieve_environment_item version_env_item;
+
+/*
+ * Initialization
+ */
+
+bool ext_environment_init(const struct sieve_extension *ext, void **context);
+void ext_environment_deinit(const struct sieve_extension *ext);
+
+/*
+ * Validator context
+ */
+
+void ext_environment_interpreter_init(const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/ext-environment.c b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment.c
new file mode 100644
index 0000000..c2130a2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/ext-environment.c
@@ -0,0 +1,59 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension variables
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5183
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-validator.h"
+
+#include "ext-environment-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_environment_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_extension_def environment_extension = {
+ .name = "environment",
+ .validator_load = ext_environment_validator_load,
+ .interpreter_load = ext_environment_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATION(tst_environment_operation)
+};
+
+static bool ext_environment_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_validator_register_command(valdtr, ext, &tst_environment);
+ return TRUE;
+}
+
+static bool ext_environment_interpreter_load
+(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ ext_environment_interpreter_init(ext, renv->interp);
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/sieve-ext-environment.h b/pigeonhole/src/lib-sieve/plugins/environment/sieve-ext-environment.h
new file mode 100644
index 0000000..8ae0d18
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/sieve-ext-environment.h
@@ -0,0 +1,54 @@
+#ifndef SIEVE_EXT_ENVIRONMENT_H
+#define SIEVE_EXT_ENVIRONMENT_H
+
+#include "sieve-common.h"
+
+/*
+ * Environment extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def environment_extension;
+
+static inline const struct sieve_extension *
+sieve_ext_environment_get_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_register
+ (svinst, &environment_extension, FALSE);
+}
+
+static inline const struct sieve_extension *
+sieve_ext_environment_require_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_require
+ (svinst, &environment_extension, TRUE);
+}
+
+bool sieve_ext_environment_is_active
+ (const struct sieve_extension *env_ext,
+ struct sieve_interpreter *interp);
+
+/*
+ * Environment item
+ */
+
+struct sieve_environment_item {
+ const char *name;
+ bool prefix;
+
+ const char *value;
+ const char *(*get_value)
+ (const struct sieve_runtime_env *renv, const char *name);
+};
+
+void sieve_environment_item_register
+ (const struct sieve_extension *env_ext, struct sieve_interpreter *interp,
+ const struct sieve_environment_item *item);
+const char *ext_environment_item_get_value
+ (const struct sieve_extension *env_ext,
+ const struct sieve_runtime_env *renv, const char *name);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/environment/tst-environment.c b/pigeonhole/src/lib-sieve/plugins/environment/tst-environment.c
new file mode 100644
index 0000000..9eaa099
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/environment/tst-environment.c
@@ -0,0 +1,215 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-environment-common.h"
+
+/*
+ * Environment test
+ *
+ * Syntax:
+ * environment [COMPARATOR] [MATCH-TYPE]
+ * <name: string> <key-list: string-list>
+ */
+
+static bool tst_environment_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_environment_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_environment_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_environment = {
+ .identifier = "environment",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_environment_registered,
+ .validate = tst_environment_validate,
+ .generate = tst_environment_generate
+};
+
+/*
+ * Environment operation
+ */
+
+static bool tst_environment_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_environment_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_environment_operation = {
+ .mnemonic = "ENVIRONMENT",
+ .ext_def = &environment_extension,
+ .dump = tst_environment_operation_dump,
+ .execute = tst_environment_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_environment_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_environment_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "name", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_environment_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_environment_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_environment_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "ENVIRONMENT");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return
+ sieve_opr_string_dump(denv, address, "name") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_environment_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ string_t *name;
+ struct sieve_stringlist *value_list, *key_list;
+ const char *env_item;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Handle match-type and comparator operands */
+ if ( sieve_match_opr_optional_read
+ (renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+ return ret;
+
+ /* Read source */
+ if ( (ret=sieve_opr_string_read(renv, address, "name", &name)) <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "environment test");
+
+ env_item = ext_environment_item_get_value
+ (this_ext, renv, str_c(name));
+
+ if ( env_item != NULL ) {
+ /* Construct value list */
+ value_list = sieve_single_stringlist_create_cstr(renv, env_item, FALSE);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret))
+ < 0 )
+ return ret;
+ } else {
+ match = 0;
+
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "environment item `%s' not found",
+ str_sanitize(str_c(name), 128));
+ }
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.am b/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.am
new file mode 100644
index 0000000..1eb0472
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.am
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libsieve_ext_ihave.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-ihave.c
+
+commands = \
+ cmd-error.c
+
+libsieve_ext_ihave_la_SOURCES = \
+ $(tests) \
+ $(commands) \
+ ext-ihave-binary.c \
+ ext-ihave-common.c \
+ ext-ihave.c
+
+noinst_HEADERS = \
+ ext-ihave-binary.h \
+ ext-ihave-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in b/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in
new file mode 100644
index 0000000..c5b8952
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in
@@ -0,0 +1,707 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/ihave
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_ihave_la_LIBADD =
+am__objects_1 = tst-ihave.lo
+am__objects_2 = cmd-error.lo
+am_libsieve_ext_ihave_la_OBJECTS = $(am__objects_1) $(am__objects_2) \
+ ext-ihave-binary.lo ext-ihave-common.lo ext-ihave.lo
+libsieve_ext_ihave_la_OBJECTS = $(am_libsieve_ext_ihave_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-error.Plo \
+ ./$(DEPDIR)/ext-ihave-binary.Plo \
+ ./$(DEPDIR)/ext-ihave-common.Plo ./$(DEPDIR)/ext-ihave.Plo \
+ ./$(DEPDIR)/tst-ihave.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_ihave_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_ihave_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_ihave.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-ihave.c
+
+commands = \
+ cmd-error.c
+
+libsieve_ext_ihave_la_SOURCES = \
+ $(tests) \
+ $(commands) \
+ ext-ihave-binary.c \
+ ext-ihave-common.c \
+ ext-ihave.c
+
+noinst_HEADERS = \
+ ext-ihave-binary.h \
+ ext-ihave-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/ihave/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/ihave/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_ihave.la: $(libsieve_ext_ihave_la_OBJECTS) $(libsieve_ext_ihave_la_DEPENDENCIES) $(EXTRA_libsieve_ext_ihave_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_ihave_la_OBJECTS) $(libsieve_ext_ihave_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-error.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-ihave-binary.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-ihave-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-ihave.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-ihave.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-error.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave-binary.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave-common.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave.Plo
+ -rm -f ./$(DEPDIR)/tst-ihave.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-error.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave-binary.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave-common.Plo
+ -rm -f ./$(DEPDIR)/ext-ihave.Plo
+ -rm -f ./$(DEPDIR)/tst-ihave.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/cmd-error.c b/pigeonhole/src/lib-sieve/plugins/ihave/cmd-error.c
new file mode 100644
index 0000000..6e971bd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/cmd-error.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * Error command
+ *
+ * Syntax
+ * error <message: string>
+ */
+
+static bool cmd_error_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_error_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def error_command = {
+ .identifier = "error",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_error_validate,
+ .generate = cmd_error_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool cmd_error_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_error_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_error_operation = {
+ .mnemonic = "ERROR",
+ .ext_def = &ihave_extension,
+ .code = EXT_IHAVE_OPERATION_ERROR,
+ .dump = cmd_error_operation_dump,
+ .execute = cmd_error_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_error_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "message", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_error_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &cmd_error_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_error_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "ERROR");
+ sieve_code_descend(denv);
+
+ return sieve_opr_string_dump(denv, address, "message");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_error_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ string_t *message;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read message */
+
+ if ( (ret=sieve_opr_string_read(renv, address, "message", &message)) <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "error \"%s\"",
+ str_sanitize(str_c(message), 80));
+
+ sieve_runtime_error(renv, NULL, "%s", str_c(message));
+
+ return SIEVE_EXEC_FAILURE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.c b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.c
new file mode 100644
index 0000000..01b375e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.c
@@ -0,0 +1,249 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-ihave-common.h"
+#include "ext-ihave-binary.h"
+
+/*
+ * Forward declarations
+ */
+
+static bool ext_ihave_binary_pre_save
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context, enum sieve_error *error_r);
+static bool ext_ihave_binary_open
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context);
+static bool ext_ihave_binary_up_to_date
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context, enum sieve_compile_flags cpflags);
+
+/*
+ * Binary include extension
+ */
+
+const struct sieve_binary_extension ihave_binary_ext = {
+ .extension = &ihave_extension,
+ .binary_pre_save = ext_ihave_binary_pre_save,
+ .binary_open = ext_ihave_binary_open,
+ .binary_up_to_date = ext_ihave_binary_up_to_date
+};
+
+/*
+ * Binary context management
+ */
+
+struct ext_ihave_binary_context {
+ struct sieve_binary *binary;
+ struct sieve_binary_block *block;
+
+ ARRAY(const char *) missing_extensions;
+};
+
+static struct ext_ihave_binary_context *ext_ihave_binary_create_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+ pool_t pool = sieve_binary_pool(sbin);
+
+ struct ext_ihave_binary_context *ctx =
+ p_new(pool, struct ext_ihave_binary_context, 1);
+
+ ctx->binary = sbin;
+ p_array_init(&ctx->missing_extensions, pool, 64);
+
+ sieve_binary_extension_set(sbin, this_ext, &ihave_binary_ext, ctx);
+ return ctx;
+}
+
+struct ext_ihave_binary_context *ext_ihave_binary_get_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+ struct ext_ihave_binary_context *ctx = (struct ext_ihave_binary_context *)
+ sieve_binary_extension_get_context(sbin, this_ext);
+
+ if ( ctx == NULL )
+ ctx = ext_ihave_binary_create_context(this_ext, sbin);
+
+ return ctx;
+}
+
+struct ext_ihave_binary_context *ext_ihave_binary_init
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+ struct sieve_ast *ast)
+{
+ struct ext_ihave_ast_context *ast_ctx =
+ ext_ihave_get_ast_context(this_ext, ast);
+ struct ext_ihave_binary_context *binctx;
+ const char *const *exts;
+ unsigned int i, count;
+
+ binctx = ext_ihave_binary_get_context(this_ext, sbin);
+
+ exts = array_get(&ast_ctx->missing_extensions, &count);
+
+ if ( count > 0 ) {
+ pool_t pool = sieve_binary_pool(sbin);
+
+ if ( binctx->block == NULL )
+ binctx->block = sieve_binary_extension_create_block(sbin, this_ext);
+
+ for ( i = 0; i < count; i++ ) {
+ const char *ext_name = p_strdup(pool, exts[i]);
+
+ array_append(&binctx->missing_extensions, &ext_name, 1);
+ }
+ }
+
+ return binctx;
+}
+
+/*
+ * Binary extension
+ */
+
+static bool ext_ihave_binary_pre_save
+(const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context, enum sieve_error *error_r ATTR_UNUSED)
+{
+ struct ext_ihave_binary_context *binctx =
+ (struct ext_ihave_binary_context *) context;
+ const char *const *exts;
+ unsigned int count, i;
+
+ exts = array_get(&binctx->missing_extensions, &count);
+
+ if ( binctx->block != NULL )
+ sieve_binary_block_clear(binctx->block);
+
+ if ( count > 0 ) {
+ if ( binctx->block == NULL )
+ binctx->block = sieve_binary_extension_create_block(sbin, ext);
+
+ sieve_binary_emit_unsigned(binctx->block, count);
+
+ for ( i = 0; i < count; i++ ) {
+ sieve_binary_emit_cstring(binctx->block, exts[i]);
+ }
+ }
+
+ return TRUE;
+}
+
+static bool ext_ihave_binary_open
+(const struct sieve_extension *ext, struct sieve_binary *sbin, void *context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_ihave_binary_context *binctx =
+ (struct ext_ihave_binary_context *) context;
+ struct sieve_binary_block *sblock;
+ unsigned int i, count, block_id;
+ sieve_size_t offset;
+
+ sblock = sieve_binary_extension_get_block(sbin, ext);
+
+ if ( sblock != NULL ) {
+ binctx->block = sblock;
+ block_id = sieve_binary_block_get_id(sblock);
+
+ offset = 0;
+
+ /* Read number of missing extensions to read subsequently */
+ if ( !sieve_binary_read_unsigned(sblock, &offset, &count) ) {
+ e_error(svinst->event, "ihave: "
+ "failed to read missing extension count "
+ "from block %d of binary %s",
+ block_id, sieve_binary_path(sbin));
+ return FALSE;
+ }
+
+ /* Read dependencies */
+ for ( i = 0; i < count; i++ ) {
+ string_t *ext_name;
+ const char *name;
+
+ if ( !sieve_binary_read_string(sblock, &offset, &ext_name) ) {
+ /* Binary is corrupt, recompile */
+ e_error(svinst->event, "ihave: "
+ "failed to read missing extension name "
+ "from block %d of binary %s",
+ block_id, sieve_binary_path(sbin));
+ return FALSE;
+ }
+
+ name = str_c(ext_name);
+ array_append(&binctx->missing_extensions, &name, 1);
+ }
+ }
+
+ return TRUE;
+}
+
+static bool ext_ihave_binary_up_to_date
+(const struct sieve_extension *ext, struct sieve_binary *sbin ATTR_UNUSED,
+ void *context, enum sieve_compile_flags cpflags)
+{
+ struct ext_ihave_binary_context *binctx =
+ (struct ext_ihave_binary_context *) context;
+ const struct sieve_extension *mext;
+ const char *const *mexts;
+ unsigned int count, i;
+
+ mexts = array_get(&binctx->missing_extensions, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( (mext=sieve_extension_get_by_name(ext->svinst, mexts[i])) != NULL &&
+ ((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) == 0 || !mext->global) )
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Main extension interface
+ */
+
+bool ext_ihave_binary_load
+(const struct sieve_extension *ext, struct sieve_binary *sbin)
+{
+ (void)ext_ihave_binary_get_context(ext, sbin);
+
+ return TRUE;
+}
+
+bool ext_ihave_binary_dump
+(const struct sieve_extension *ext, struct sieve_dumptime_env *denv)
+{
+ struct sieve_binary *sbin = denv->sbin;
+ struct ext_ihave_binary_context *binctx =
+ ext_ihave_binary_get_context(ext, sbin);
+ const char *const *exts;
+ unsigned int count, i;
+
+ exts = array_get(&binctx->missing_extensions, &count);
+
+ if ( count > 0 ) {
+ sieve_binary_dump_sectionf(denv,
+ "Extensions missing at compile (block: %d)",
+ sieve_binary_block_get_id(binctx->block));
+
+ for ( i = 0; i < count; i++ ) {
+ sieve_binary_dumpf(denv, " - %s\n", exts[i]);
+ }
+ }
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.h b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.h
new file mode 100644
index 0000000..418f078
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-binary.h
@@ -0,0 +1,33 @@
+#ifndef EXT_IHAVE_BINARY_H
+#define EXT_IHAVE_BINARY_H
+
+/*
+ * Binary context management
+ */
+
+struct ext_ihave_binary_context;
+
+struct ext_ihave_binary_context *ext_ihave_binary_get_context
+ (const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+struct ext_ihave_binary_context *ext_ihave_binary_init
+ (const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+ struct sieve_ast *ast);
+
+/*
+ * Registering missing extension
+ */
+
+void ext_ihave_binary_add_missing_extension
+ (struct ext_ihave_binary_context *binctx, const char *ext_name);
+
+/*
+ * Main extension interface
+ */
+
+bool ext_ihave_binary_load
+ (const struct sieve_extension *ext, struct sieve_binary *sbin);
+bool ext_ihave_binary_dump
+ (const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.c b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.c
new file mode 100644
index 0000000..1d31238
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * AST context management
+ */
+
+struct ext_ihave_ast_context *ext_ihave_get_ast_context
+(const struct sieve_extension *this_ext, struct sieve_ast *ast)
+{
+ struct ext_ihave_ast_context *actx = (struct ext_ihave_ast_context *)
+ sieve_ast_extension_get_context(ast, this_ext);
+ pool_t pool;
+
+ if ( actx != NULL )
+ return actx;
+
+ pool = sieve_ast_pool(ast);
+ actx = p_new(pool, struct ext_ihave_ast_context, 1);
+ p_array_init(&actx->missing_extensions, pool, 64);
+
+ sieve_ast_extension_set_context(ast, this_ext, (void *) actx);
+
+ return actx;
+}
+
+void ext_ihave_ast_add_missing_extension
+(const struct sieve_extension *this_ext, struct sieve_ast *ast,
+ const char *ext_name)
+{
+ struct ext_ihave_ast_context *actx =
+ ext_ihave_get_ast_context(this_ext, ast);
+ const char *const *exts;
+ unsigned int i, count;
+
+ exts = array_get(&actx->missing_extensions, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( strcmp(exts[i], ext_name) == 0 )
+ return;
+ }
+
+ array_append(&actx->missing_extensions, &ext_name, 1);
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.h b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.h
new file mode 100644
index 0000000..371d03d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave-common.h
@@ -0,0 +1,52 @@
+#ifndef EXT_IHAVE_COMMON_H
+#define EXT_IHAVE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def ihave_extension;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def ihave_test;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def error_command;
+
+/*
+ * Operations
+ */
+
+enum ext_ihave_opcode {
+ EXT_IHAVE_OPERATION_IHAVE,
+ EXT_IHAVE_OPERATION_ERROR
+};
+
+extern const struct sieve_operation_def tst_ihave_operation;
+extern const struct sieve_operation_def cmd_error_operation;
+
+/*
+ * AST context
+ */
+
+struct ext_ihave_ast_context {
+ ARRAY(const char *) missing_extensions;
+};
+
+struct ext_ihave_ast_context *ext_ihave_get_ast_context
+ (const struct sieve_extension *this_ext, struct sieve_ast *ast);
+
+void ext_ihave_ast_add_missing_extension
+ (const struct sieve_extension *this_ext, struct sieve_ast *ast,
+ const char *ext_name);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave.c b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave.c
new file mode 100644
index 0000000..40b70eb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/ext-ihave.c
@@ -0,0 +1,70 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension ihave
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5463
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-ihave-common.h"
+#include "ext-ihave-binary.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_ihave_operations[] = {
+ &tst_ihave_operation,
+ &cmd_error_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_ihave_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_ihave_generator_load
+ (const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+
+const struct sieve_extension_def ihave_extension = {
+ "ihave",
+ .version = 1,
+ .validator_load = ext_ihave_validator_load,
+ .generator_load = ext_ihave_generator_load,
+ .binary_load = ext_ihave_binary_load,
+ .binary_dump = ext_ihave_binary_dump,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_ihave_operations)
+};
+
+static bool ext_ihave_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ sieve_validator_register_command(validator, ext, &ihave_test);
+ sieve_validator_register_command(validator, ext, &error_command);
+
+ return TRUE;
+}
+
+static bool ext_ihave_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+ (void)ext_ihave_binary_init(ext, cgenv->sbin, cgenv->ast);
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c b/pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c
new file mode 100644
index 0000000..c6c8e8f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c
@@ -0,0 +1,294 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-ihave-common.h"
+
+/*
+ * Ihave test
+ *
+ * Syntax:
+ * ihave <capabilities: string-list>
+ */
+
+static bool
+tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool
+tst_ihave_validate_const(struct sieve_validator *valdtr,
+ struct sieve_command *tst, int *const_current,
+ int const_next);
+static bool
+tst_ihave_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst);
+
+const struct sieve_command_def ihave_test = {
+ .identifier = "ihave",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_ihave_validate,
+ .validate_const = tst_ihave_validate_const,
+ .generate = tst_ihave_generate
+};
+
+/*
+ * Ihave operation
+ */
+
+static bool
+tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def tst_ihave_operation = {
+ .mnemonic = "IHAVE",
+ .ext_def = &ihave_extension,
+ .code = EXT_IHAVE_OPERATION_IHAVE,
+ .dump = tst_ihave_operation_dump,
+ .execute = tst_ihave_operation_execute
+};
+
+/*
+ * Code validation
+ */
+
+static bool
+tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct _capability {
+ const struct sieve_extension *ext;
+ struct sieve_ast_argument *arg;
+ };
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *stritem;
+ enum sieve_compile_flags cpflags =
+ sieve_validator_compile_flags(valdtr);
+ bool no_global = ((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0);
+ ARRAY(struct _capability) capabilities;
+ struct _capability capability;
+ const struct _capability *caps;
+ unsigned int i, count;
+ bool all_known = TRUE;
+
+ t_array_init(&capabilities, 64);
+
+ tst->data = (void *)FALSE;
+
+ /* Check stringlist argument */
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "capabilities", 1,
+ SAAT_STRING_LIST))
+ return FALSE;
+
+ switch (sieve_ast_argument_type(arg)) {
+ case SAAT_STRING:
+ /* Single string */
+ capability.arg = arg;
+ capability.ext = sieve_extension_get_by_name(
+ tst->ext->svinst, sieve_ast_argument_strc(arg));
+
+ if (capability.ext == NULL ||
+ (no_global && capability.ext->global)) {
+ all_known = FALSE;
+
+ ext_ihave_ast_add_missing_extension(
+ tst->ext, tst->ast_node->ast,
+ sieve_ast_argument_strc(arg));
+ } else {
+ array_append(&capabilities, &capability, 1);
+ }
+
+ break;
+
+ case SAAT_STRING_LIST:
+ /* String list */
+ stritem = sieve_ast_strlist_first(arg);
+
+ while (stritem != NULL) {
+ capability.arg = stritem;
+ capability.ext = sieve_extension_get_by_name(
+ tst->ext->svinst,
+ sieve_ast_argument_strc(stritem));
+
+ if (capability.ext == NULL ||
+ (no_global && capability.ext->global)) {
+ all_known = FALSE;
+
+ ext_ihave_ast_add_missing_extension(
+ tst->ext, tst->ast_node->ast,
+ sieve_ast_argument_strc(stritem));
+ } else {
+ array_append(&capabilities, &capability, 1);
+ }
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+
+ break;
+ default:
+ i_unreached();
+ }
+
+ if (!all_known)
+ return TRUE;
+
+ /* RFC 5463, Section 4, page 4:
+
+ The "ihave" extension is designed to be used with other extensions
+ that add tests, actions, comparators, or arguments. Implementations
+ MUST NOT allow it to be used with extensions that change the
+ underlying Sieve grammar, or extensions like encoded-character
+ [RFC5228], or variables [RFC5229] that change how the content of
+ Sieve scripts are interpreted. The test MUST fail and the extension
+ MUST NOT be enabled if such usage is attempted.
+
+ FIXME: current implementation of this restriction is hardcoded and
+ therefore highly inflexible
+ */
+ caps = array_get(&capabilities, &count);
+ for (i = 0; i < count; i++) {
+ if (sieve_extension_name_is(caps[i].ext, "variables") ||
+ sieve_extension_name_is(caps[i].ext, "encoded-character"))
+ return TRUE;
+ }
+
+ /* Load all extensions */
+ caps = array_get(&capabilities, &count);
+ for (i = 0; i < count; i++) {
+ if (!sieve_validator_extension_load(valdtr, tst, caps[i].arg,
+ caps[i].ext, FALSE))
+ return FALSE;
+ }
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ tst->data = (void *)TRUE;
+ return TRUE;
+}
+
+static bool
+tst_ihave_validate_const(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst, int *const_current,
+ int const_next ATTR_UNUSED)
+{
+ if ((bool)tst->data == TRUE)
+ *const_current = -1;
+ else
+ *const_current = 0;
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+bool tst_ihave_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ /* Emit opcode */
+ sieve_operation_emit(cgenv->sblock, tst->ext, &tst_ihave_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "IHAVE");
+ sieve_code_descend(denv);
+
+ return sieve_opr_stringlist_dump(denv, address, "capabilities");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_stringlist *capabilities;
+ string_t *cap_item;
+ bool matched;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read capabilities */
+ if ((ret = sieve_opr_stringlist_read(renv, address, "capabilities",
+ &capabilities)) <= 0)
+ return ret;
+
+ /*
+ * Perform test
+ */
+
+ /* Perform the test */
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "ihave test");
+ sieve_runtime_trace_descend(renv);
+
+ cap_item = NULL;
+ matched = TRUE;
+ while (matched &&
+ (ret = sieve_stringlist_next_item(capabilities,
+ &cap_item)) > 0) {
+ const struct sieve_extension *ext;
+ int sret;
+
+ ext = sieve_extension_get_by_name(svinst, str_c(cap_item));
+ if (ext == NULL) {
+ sieve_runtime_trace_error(
+ renv, "ihave: invalid extension name");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ sret = sieve_interpreter_extension_start(renv->interp, ext);
+ if (sret == SIEVE_EXEC_FAILURE) {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "extension `%s' not available",
+ sieve_extension_name(ext));
+ matched = FALSE;
+ } else if (sret == SIEVE_EXEC_OK) {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "extension `%s' available",
+ sieve_extension_name(ext));
+ } else {
+ return sret;
+ }
+ }
+ if (ret < 0) {
+ sieve_runtime_trace_error(renv, "invalid capabilities item");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, matched);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.am b/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.am
new file mode 100644
index 0000000..e5390d4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.am
@@ -0,0 +1,33 @@
+noinst_LTLIBRARIES = libsieve_ext_imap4flags.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-flag.c
+
+tests = \
+ tst-hasflag.c
+
+tags = \
+ tag-flags.c
+
+libsieve_ext_imap4flags_la_SOURCES = \
+ ext-imap4flags-common.c \
+ $(commands) \
+ $(tests) \
+ $(tags) \
+ ext-imap4flags.c \
+ ext-imapflags.c
+
+public_headers = \
+ sieve-ext-imap4flags.h
+
+headers = \
+ ext-imap4flags-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in b/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in
new file mode 100644
index 0000000..77f2dc3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in
@@ -0,0 +1,776 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/imap4flags
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_imap4flags_la_LIBADD =
+am__objects_1 = cmd-flag.lo
+am__objects_2 = tst-hasflag.lo
+am__objects_3 = tag-flags.lo
+am_libsieve_ext_imap4flags_la_OBJECTS = ext-imap4flags-common.lo \
+ $(am__objects_1) $(am__objects_2) $(am__objects_3) \
+ ext-imap4flags.lo ext-imapflags.lo
+libsieve_ext_imap4flags_la_OBJECTS = \
+ $(am_libsieve_ext_imap4flags_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-flag.Plo \
+ ./$(DEPDIR)/ext-imap4flags-common.Plo \
+ ./$(DEPDIR)/ext-imap4flags.Plo ./$(DEPDIR)/ext-imapflags.Plo \
+ ./$(DEPDIR)/tag-flags.Plo ./$(DEPDIR)/tst-hasflag.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_imap4flags_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_imap4flags_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_imap4flags.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-flag.c
+
+tests = \
+ tst-hasflag.c
+
+tags = \
+ tag-flags.c
+
+libsieve_ext_imap4flags_la_SOURCES = \
+ ext-imap4flags-common.c \
+ $(commands) \
+ $(tests) \
+ $(tags) \
+ ext-imap4flags.c \
+ ext-imapflags.c
+
+public_headers = \
+ sieve-ext-imap4flags.h
+
+headers = \
+ ext-imap4flags-common.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/imap4flags/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/imap4flags/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_imap4flags.la: $(libsieve_ext_imap4flags_la_OBJECTS) $(libsieve_ext_imap4flags_la_DEPENDENCIES) $(EXTRA_libsieve_ext_imap4flags_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_imap4flags_la_OBJECTS) $(libsieve_ext_imap4flags_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-flag.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-imap4flags-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-imap4flags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-imapflags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag-flags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-hasflag.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-flag.Plo
+ -rm -f ./$(DEPDIR)/ext-imap4flags-common.Plo
+ -rm -f ./$(DEPDIR)/ext-imap4flags.Plo
+ -rm -f ./$(DEPDIR)/ext-imapflags.Plo
+ -rm -f ./$(DEPDIR)/tag-flags.Plo
+ -rm -f ./$(DEPDIR)/tst-hasflag.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-flag.Plo
+ -rm -f ./$(DEPDIR)/ext-imap4flags-common.Plo
+ -rm -f ./$(DEPDIR)/ext-imap4flags.Plo
+ -rm -f ./$(DEPDIR)/ext-imapflags.Plo
+ -rm -f ./$(DEPDIR)/tag-flags.Plo
+ -rm -f ./$(DEPDIR)/tst-hasflag.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/cmd-flag.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/cmd-flag.c
new file mode 100644
index 0000000..2645669
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/cmd-flag.c
@@ -0,0 +1,251 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Commands
+ */
+
+/* Forward declarations */
+
+static bool cmd_flag_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Setflag command
+ *
+ * Syntax:
+ * setflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_setflag = {
+ .identifier = "setflag",
+ .type = SCT_COMMAND,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = ext_imap4flags_command_validate,
+ .generate = cmd_flag_generate
+};
+
+/* Addflag command
+ *
+ * Syntax:
+ * addflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_addflag = {
+ .identifier = "addflag",
+ .type = SCT_COMMAND,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = ext_imap4flags_command_validate,
+ .generate = cmd_flag_generate
+};
+
+
+/* Removeflag command
+ *
+ * Syntax:
+ * removeflag [<variablename: string>] <list-of-flags: string-list>
+ */
+
+const struct sieve_command_def cmd_removeflag = {
+ .identifier = "removeflag",
+ .type = SCT_COMMAND,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = ext_imap4flags_command_validate,
+ .generate = cmd_flag_generate
+};
+
+/*
+ * Operations
+ */
+
+/* Forward declarations */
+
+bool cmd_flag_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_flag_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Setflag operation */
+
+const struct sieve_operation_def setflag_operation = {
+ .mnemonic = "SETFLAG",
+ .ext_def = &imap4flags_extension,
+ .code = EXT_IMAP4FLAGS_OPERATION_SETFLAG,
+ .dump = cmd_flag_operation_dump,
+ .execute = cmd_flag_operation_execute
+};
+
+/* Addflag operation */
+
+const struct sieve_operation_def addflag_operation = {
+ .mnemonic = "ADDFLAG",
+ .ext_def = &imap4flags_extension,
+ .code = EXT_IMAP4FLAGS_OPERATION_ADDFLAG,
+ .dump = cmd_flag_operation_dump,
+ .execute = cmd_flag_operation_execute
+};
+
+/* Removeflag operation */
+
+const struct sieve_operation_def removeflag_operation = {
+ .mnemonic = "REMOVEFLAG",
+ .ext_def = &imap4flags_extension,
+ .code = EXT_IMAP4FLAGS_OPERATION_REMOVEFLAG,
+ .dump = cmd_flag_operation_dump,
+ .execute = cmd_flag_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_flag_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg1, *arg2;
+
+ /* Emit operation */
+ if ( sieve_command_is(cmd, cmd_setflag) )
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &setflag_operation);
+ else if ( sieve_command_is(cmd, cmd_addflag) )
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &addflag_operation);
+ else if ( sieve_command_is(cmd, cmd_removeflag) )
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &removeflag_operation);
+
+ arg1 = cmd->first_positional;
+ arg2 = sieve_ast_argument_next(arg1);
+
+ if ( arg2 == NULL ) {
+ /* No variable */
+ sieve_opr_omitted_emit(cgenv->sblock);
+ if ( !sieve_generate_argument(cgenv, arg1, cmd) )
+ return FALSE;
+ } else {
+ /* Full command */
+ if ( !sieve_generate_argument(cgenv, arg1, cmd) )
+ return FALSE;
+ if ( !sieve_generate_argument(cgenv, arg2, cmd) )
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+bool cmd_flag_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ struct sieve_operand oprnd;
+
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+ sieve_code_descend(denv);
+
+ sieve_code_mark(denv);
+ if ( !sieve_operand_read(denv->sblock, address, NULL, &oprnd) ) {
+ sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+ return FALSE;
+ }
+
+ if ( !sieve_operand_is_omitted(&oprnd) ) {
+ return
+ sieve_opr_string_dump_data(denv, &oprnd, address, "variable name") &&
+ sieve_opr_stringlist_dump(denv, address, "list of flags");
+ }
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "list of flags");
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_flag_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *op = renv->oprtn;
+ struct sieve_operand oprnd;
+ struct sieve_stringlist *flag_list;
+ struct sieve_variable_storage *storage;
+ unsigned int var_index;
+ ext_imapflag_flag_operation_t flag_op;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read bare operand (two types possible) */
+ if ( (ret=sieve_operand_runtime_read
+ (renv, address, NULL, &oprnd)) <= 0 )
+ return ret;
+
+ /* Variable operand (optional) */
+ if ( !sieve_operand_is_omitted(&oprnd) ) {
+ /* Read the variable operand */
+ if ( (ret=sieve_variable_operand_read_data
+ (renv, &oprnd, address, "variable", &storage, &var_index)) <= 0 )
+ return ret;
+
+ /* Read flag list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "flag-list", &flag_list))
+ <= 0 )
+ return ret;
+
+ /* Flag-list operand */
+ } else {
+ storage = NULL;
+ var_index = 0;
+
+ /* Read flag list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address,
+ "flag-list", &flag_list)) <= 0 )
+ return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ /* Determine what to do */
+
+ if ( sieve_operation_is(op, setflag_operation) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "setflag command");
+ flag_op = sieve_ext_imap4flags_set_flags;
+ } else if ( sieve_operation_is(op, addflag_operation) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "addflag command");
+ flag_op = sieve_ext_imap4flags_add_flags;
+ } else if ( sieve_operation_is(op, removeflag_operation) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "removeflag command");
+ flag_op = sieve_ext_imap4flags_remove_flags;
+ } else {
+ i_unreached();
+ }
+
+ sieve_runtime_trace_descend(renv);
+
+ /* Perform requested operation */
+ return flag_op(renv, op->ext, storage, var_index, flag_list);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
new file mode 100644
index 0000000..3c364b8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.c
@@ -0,0 +1,733 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "imap-arg.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def tag_flags;
+extern const struct sieve_argument_def tag_flags_implicit;
+
+/*
+ * Common command functions
+ */
+
+bool ext_imap4flags_command_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct sieve_ast_argument *arg2;
+ const struct sieve_extension *var_ext;
+
+ /* Check arguments */
+
+ if ( arg == NULL ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the %s %s expects at least one argument, but none was found",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ if ( sieve_ast_argument_type(arg) != SAAT_STRING &&
+ sieve_ast_argument_type(arg) != SAAT_STRING_LIST )
+ {
+ sieve_argument_validate_error(valdtr, arg,
+ "the %s %s expects either a string (variable name) or "
+ "a string-list (list of flags) as first argument, but %s was found",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ arg2 = sieve_ast_argument_next(arg);
+ if ( arg2 != NULL ) {
+ /* First, check syntax sanity */
+
+ if ( sieve_ast_argument_type(arg) != SAAT_STRING )
+ {
+ if ( sieve_command_is(cmd, tst_hasflag) ) {
+ if ( sieve_ast_argument_type(arg) != SAAT_STRING_LIST ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "if a second argument is specified for the hasflag, the first "
+ "must be a string-list (variable-list), but %s was found",
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+ } else {
+ sieve_argument_validate_error(valdtr, arg,
+ "if a second argument is specified for the %s %s, the first "
+ "must be a string (variable name), but %s was found",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+ }
+
+ /* Then, check whether the second argument is permitted */
+
+ var_ext = sieve_ext_variables_get_extension(cmd->ext->svinst);
+
+ if ( var_ext == NULL || !sieve_ext_variables_is_active(var_ext, valdtr) )
+ {
+ sieve_argument_validate_error(valdtr,arg,
+ "the %s %s only allows for the specification of a "
+ "variable name when the variables extension is active",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ if ( !sieve_variable_argument_activate(var_ext, var_ext,
+ valdtr, cmd, arg, !sieve_command_is(cmd, tst_hasflag) ) )
+ return FALSE;
+
+ if ( sieve_ast_argument_type(arg2) != SAAT_STRING &&
+ sieve_ast_argument_type(arg2) != SAAT_STRING_LIST )
+ {
+ sieve_argument_validate_error(valdtr, arg2,
+ "the %s %s expects a string list (list of flags) as "
+ "second argument when two arguments are specified, "
+ "but %s was found",
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd),
+ sieve_ast_argument_name(arg2));
+ return FALSE;
+ }
+ } else
+ arg2 = arg;
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg2, FALSE) )
+ return FALSE;
+
+ if ( !sieve_command_is(cmd, tst_hasflag) &&
+ sieve_argument_is_string_literal(arg2) ) {
+ struct ext_imap4flags_iter fiter;
+ const char *flag;
+
+ /* Warn the user about validity of verifiable flags */
+ ext_imap4flags_iter_init(&fiter, sieve_ast_argument_str(arg));
+
+ while ( (flag=ext_imap4flags_iter_get_flag(&fiter)) != NULL ) {
+ if ( !sieve_ext_imap4flags_flag_is_valid(flag) ) {
+ sieve_argument_validate_warning(valdtr, arg,
+ "IMAP flag '%s' specified for the %s command is invalid "
+ "and will be ignored (only first invalid is reported)",
+ str_sanitize(flag, 64), sieve_command_identifier(cmd));
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Flags tag registration
+ */
+
+void ext_imap4flags_attach_flags_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const char *command)
+{
+ /* Register :flags tag with the command and we don't care whether it is
+ * registered or even whether it will be registered at all. The validator
+ * handles either situation gracefully
+ */
+
+ /* Tag specified by user */
+ sieve_validator_register_external_tag
+ (valdtr, command, ext, &tag_flags, SIEVE_OPT_SIDE_EFFECT);
+}
+
+void sieve_ext_imap4flags_register_side_effect
+(struct sieve_validator *valdtr, const struct sieve_extension *flg_ext,
+ const char *command)
+{
+ /* Implicit tag if none is specified */
+ sieve_validator_register_persistent_tag
+ (valdtr, command, flg_ext, &tag_flags_implicit);
+}
+
+
+/*
+ * Result context
+ */
+
+struct ext_imap4flags_result_context {
+ string_t *internal_flags;
+};
+
+static void _get_initial_flags
+(struct sieve_result *result, string_t *flags)
+{
+ const struct sieve_message_data *msgdata =
+ sieve_result_get_message_data(result);
+ enum mail_flags mail_flags;
+ const char *const *mail_keywords;
+
+ mail_flags = mail_get_flags(msgdata->mail);
+ mail_keywords = mail_get_keywords(msgdata->mail);
+
+ if ( (mail_flags & MAIL_FLAGGED) > 0 )
+ str_printfa(flags, " \\flagged");
+
+ if ( (mail_flags & MAIL_ANSWERED) > 0 )
+ str_printfa(flags, " \\answered");
+
+ if ( (mail_flags & MAIL_DELETED) > 0 )
+ str_printfa(flags, " \\deleted");
+
+ if ( (mail_flags & MAIL_SEEN) > 0 )
+ str_printfa(flags, " \\seen");
+
+ if ( (mail_flags & MAIL_DRAFT) > 0 )
+ str_printfa(flags, " \\draft");
+
+ while ( *mail_keywords != NULL ) {
+ str_printfa(flags, " %s", *mail_keywords);
+ mail_keywords++;
+ }
+}
+
+static inline struct ext_imap4flags_result_context *_get_result_context
+(const struct sieve_extension *this_ext, struct sieve_result *result)
+{
+ struct ext_imap4flags_result_context *rctx =
+ (struct ext_imap4flags_result_context *)
+ sieve_result_extension_get_context(result, this_ext);
+
+ if ( rctx == NULL ) {
+ pool_t pool = sieve_result_pool(result);
+
+ rctx =p_new(pool, struct ext_imap4flags_result_context, 1);
+ rctx->internal_flags = str_new(pool, 32);
+ _get_initial_flags(result, rctx->internal_flags);
+
+ sieve_result_extension_set_context
+ (result, this_ext, rctx);
+ }
+
+ return rctx;
+}
+
+static string_t *_get_flags_string
+(const struct sieve_extension *this_ext, struct sieve_result *result)
+{
+ struct ext_imap4flags_result_context *ctx =
+ _get_result_context(this_ext, result);
+
+ return ctx->internal_flags;
+}
+
+/*
+ * Runtime initialization
+ */
+
+static int ext_imap4flags_runtime_init
+(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context ATTR_UNUSED, bool deferred ATTR_UNUSED)
+{
+ sieve_result_add_implicit_side_effect
+ (renv->result, NULL, TRUE, ext, &flags_side_effect, NULL);
+ return SIEVE_EXEC_OK;
+}
+
+const struct sieve_interpreter_extension
+imap4flags_interpreter_extension = {
+ .ext_def = &imap4flags_extension,
+ .run = ext_imap4flags_runtime_init
+};
+
+/*
+ * Flag handling
+ */
+
+/* FIXME: This currently accepts a potentially unlimited number of
+ * flags, making the internal or variable flag list indefinitely long
+ */
+
+bool sieve_ext_imap4flags_flag_is_valid(const char *flag)
+{
+ if ( *flag == '\0' )
+ return FALSE;
+
+ if ( *flag == '\\' ) {
+ /* System flag */
+ const char *atom = t_str_ucase(flag);
+
+ if (
+ (strcmp(atom, "\\ANSWERED") != 0) &&
+ (strcmp(atom, "\\FLAGGED") != 0) &&
+ (strcmp(atom, "\\DELETED") != 0) &&
+ (strcmp(atom, "\\SEEN") != 0) &&
+ (strcmp(atom, "\\DRAFT") != 0) )
+ {
+ return FALSE;
+ }
+ } else {
+ const char *p;
+
+ /* Custom keyword:
+ *
+ * Syntax (IMAP4rev1, RFC 3501, Section 9. Formal Syntax) :
+ * flag-keyword = atom
+ * atom = 1*ATOM-CHAR
+ */
+ p = flag;
+ while ( *p != '\0' ) {
+ if ( !IS_ATOM_CHAR(*p) )
+ return FALSE;
+ p++;
+ }
+ }
+
+ return TRUE;
+}
+
+/* Flag iterator */
+
+static void ext_imap4flags_iter_clear
+(struct ext_imap4flags_iter *iter)
+{
+ i_zero(iter);
+}
+
+void ext_imap4flags_iter_init
+(struct ext_imap4flags_iter *iter, string_t *flags_list)
+{
+ ext_imap4flags_iter_clear(iter);
+ iter->flags_list = flags_list;
+}
+
+static string_t *ext_imap4flags_iter_get_flag_str
+(struct ext_imap4flags_iter *iter)
+{
+ unsigned int len;
+ const unsigned char *fp;
+ const unsigned char *fbegin;
+ const unsigned char *fstart;
+ const unsigned char *fend;
+
+ /* Return if not initialized */
+ if ( iter->flags_list == NULL ) return NULL;
+
+ /* Return if no more flags are available */
+ len = str_len(iter->flags_list);
+ if ( iter->offset >= len ) return NULL;
+
+ /* Mark string boundries */
+ fbegin = str_data(iter->flags_list);
+ fend = fbegin + len;
+
+ /* Start of this flag */
+ fstart = fbegin + iter->offset;
+
+ /* Scan for next flag */
+ fp = fstart;
+ for (;;) {
+ /* Have we reached the end or a flag boundary? */
+ if ( fp >= fend || *fp == ' ' ) {
+ /* Did we scan more than nothing ? */
+ if ( fp > fstart ) {
+ /* Return flag */
+ string_t *flag = t_str_new(fp-fstart+1);
+ str_append_data(flag, fstart, fp-fstart);
+
+ iter->last = fstart - fbegin;
+ iter->offset = fp - fbegin;
+
+ return flag;
+ }
+
+ fstart = fp + 1;
+ }
+
+ if ( fp >= fend ) break;
+
+ fp++;
+ }
+
+ iter->last = fstart - fbegin;
+ iter->offset = fp - fbegin;
+ return NULL;
+}
+
+const char *ext_imap4flags_iter_get_flag
+(struct ext_imap4flags_iter *iter)
+{
+ string_t *flag = ext_imap4flags_iter_get_flag_str(iter);
+
+ if ( flag == NULL ) return NULL;
+
+ return str_c(flag);
+}
+
+static void ext_imap4flags_iter_delete_last
+(struct ext_imap4flags_iter *iter)
+{
+ iter->offset++;
+ if ( iter->offset > str_len(iter->flags_list) )
+ iter->offset = str_len(iter->flags_list);
+ if ( iter->offset == str_len(iter->flags_list) && iter->last > 0 )
+ iter->last--;
+
+ str_delete(iter->flags_list, iter->last, iter->offset - iter->last);
+
+ iter->offset = iter->last;
+}
+
+/* Flag operations */
+
+static string_t *ext_imap4flags_get_flag_variable
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index)
+ ATTR_NULL(2);
+
+static bool flags_list_flag_exists
+(string_t *flags_list, const char *flag)
+{
+ const char *flg;
+ struct ext_imap4flags_iter flit;
+
+ ext_imap4flags_iter_init(&flit, flags_list);
+
+ while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+ if ( strcasecmp(flg, flag) == 0 )
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void flags_list_flag_delete
+(string_t *flags_list, const char *flag)
+{
+ const char *flg;
+ struct ext_imap4flags_iter flit;
+
+ ext_imap4flags_iter_init(&flit, flags_list);
+
+ while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+ if ( strcasecmp(flg, flag) == 0 ) {
+ ext_imap4flags_iter_delete_last(&flit);
+ }
+ }
+}
+
+static void flags_list_add_flags
+(string_t *flags_list, string_t *flags)
+{
+ const char *flg;
+ struct ext_imap4flags_iter flit;
+
+ ext_imap4flags_iter_init(&flit, flags);
+
+ while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+ if ( sieve_ext_imap4flags_flag_is_valid(flg) &&
+ !flags_list_flag_exists(flags_list, flg) ) {
+ if ( str_len(flags_list) != 0 )
+ str_append_c(flags_list, ' ');
+ str_append(flags_list, flg);
+ }
+ }
+}
+
+static void flags_list_remove_flags
+(string_t *flags_list, string_t *flags)
+{
+ const char *flg;
+ struct ext_imap4flags_iter flit;
+
+ ext_imap4flags_iter_init(&flit, flags);
+
+ while ( (flg=ext_imap4flags_iter_get_flag(&flit)) != NULL ) {
+ flags_list_flag_delete(flags_list, flg);
+ }
+}
+
+static void flags_list_set_flags
+(string_t *flags_list, string_t *flags)
+{
+ str_truncate(flags_list, 0);
+ flags_list_add_flags(flags_list, flags);
+}
+
+static void flags_list_clear_flags
+(string_t *flags_list)
+{
+ str_truncate(flags_list, 0);
+}
+
+static string_t *ext_imap4flags_get_flag_variable
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index)
+{
+ string_t *flags;
+
+ if ( storage != NULL ) {
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ const char *var_name, *var_id;
+
+ (void)sieve_variable_get_identifier(storage, var_index, &var_name);
+ var_id = sieve_variable_get_varid(storage, var_index);
+
+ sieve_runtime_trace(renv, 0, "update variable `%s' [%s]",
+ var_name, var_id);
+ }
+
+ if ( !sieve_variable_get_modifiable(storage, var_index, &flags) )
+ return NULL;
+ } else {
+ i_assert( sieve_extension_is(flg_ext, imap4flags_extension) );
+ flags = _get_flags_string(flg_ext, renv->result);
+ }
+
+ return flags;
+}
+
+int sieve_ext_imap4flags_set_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags)
+{
+ string_t *cur_flags = ext_imap4flags_get_flag_variable
+ (renv, flg_ext, storage, var_index);
+
+ if ( cur_flags != NULL ) {
+ string_t *flags_item;
+ int ret;
+
+ flags_list_clear_flags(cur_flags);
+ while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "set flags `%s'", str_c(flags_item));
+
+ flags_list_add_flags(cur_flags, flags_item);
+ }
+
+ if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+ return SIEVE_EXEC_OK;
+ }
+
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_ext_imap4flags_add_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags)
+{
+ string_t *cur_flags = ext_imap4flags_get_flag_variable
+ (renv, flg_ext, storage, var_index);
+
+ if ( cur_flags != NULL ) {
+ string_t *flags_item;
+ int ret;
+
+ while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "add flags `%s'", str_c(flags_item));
+
+ flags_list_add_flags(cur_flags, flags_item);
+ }
+
+ if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+ return SIEVE_EXEC_OK;
+ }
+
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_ext_imap4flags_remove_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags)
+{
+ string_t *cur_flags = ext_imap4flags_get_flag_variable
+ (renv, flg_ext, storage, var_index);
+
+ if ( cur_flags != NULL ) {
+ string_t *flags_item;
+ int ret;
+
+ while ( (ret=sieve_stringlist_next_item(flags, &flags_item)) > 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "remove flags `%s'", str_c(flags_item));
+
+ flags_list_remove_flags(cur_flags, flags_item);
+ }
+
+ if ( ret < 0 ) return SIEVE_EXEC_BIN_CORRUPT;
+
+ return SIEVE_EXEC_OK;
+ }
+
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+/* Flag stringlist */
+
+static int ext_imap4flags_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void ext_imap4flags_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+
+struct ext_imap4flags_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_stringlist *flags_list;
+ string_t *flags_string;
+ struct ext_imap4flags_iter flit;
+
+ bool normalize:1;
+};
+
+static struct sieve_stringlist *ext_imap4flags_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *flags_list,
+ bool normalize)
+{
+ struct ext_imap4flags_stringlist *strlist;
+
+ strlist = t_new(struct ext_imap4flags_stringlist, 1);
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.runenv = renv;
+ strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+ strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+ strlist->normalize = normalize;
+
+ strlist->flags_list = flags_list;
+
+ return &strlist->strlist;
+}
+
+static struct sieve_stringlist *ext_imap4flags_stringlist_create_single
+(const struct sieve_runtime_env *renv, string_t *flags_string, bool normalize)
+{
+ struct ext_imap4flags_stringlist *strlist;
+
+ strlist = t_new(struct ext_imap4flags_stringlist, 1);
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.runenv = renv;
+ strlist->strlist.next_item = ext_imap4flags_stringlist_next_item;
+ strlist->strlist.reset = ext_imap4flags_stringlist_reset;
+ strlist->normalize = normalize;
+
+ if ( normalize ) {
+ strlist->flags_string = t_str_new(256);
+ flags_list_set_flags(strlist->flags_string, flags_string);
+ } else {
+ strlist->flags_string = flags_string;
+ }
+
+ ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+
+ return &strlist->strlist;
+}
+
+static int ext_imap4flags_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct ext_imap4flags_stringlist *strlist =
+ (struct ext_imap4flags_stringlist *)_strlist;
+
+ while ( (*str_r=ext_imap4flags_iter_get_flag_str(&strlist->flit)) == NULL ) {
+ int ret;
+
+ if ( strlist->flags_list == NULL )
+ return 0;
+
+ if ( (ret=sieve_stringlist_next_item
+ (strlist->flags_list, &strlist->flags_string)) <= 0 )
+ return ret;
+
+ if ( strlist->flags_string == NULL )
+ return -1;
+
+ if ( strlist->normalize ) {
+ string_t *flags_string = t_str_new(256);
+
+ flags_list_set_flags(flags_string, strlist->flags_string);
+ strlist->flags_string = flags_string;
+ }
+
+ ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+ }
+
+ return 1;
+}
+
+static void ext_imap4flags_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct ext_imap4flags_stringlist *strlist =
+ (struct ext_imap4flags_stringlist *)_strlist;
+
+ if ( strlist->flags_list != NULL ) {
+ sieve_stringlist_reset(strlist->flags_list);
+ ext_imap4flags_iter_clear(&strlist->flit);
+ } else {
+ ext_imap4flags_iter_init(&strlist->flit, strlist->flags_string);
+ }
+}
+
+/* Flag access */
+
+struct sieve_stringlist *sieve_ext_imap4flags_get_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_stringlist *flags_list)
+{
+ if ( flags_list == NULL ) {
+ i_assert( sieve_extension_is(flg_ext, imap4flags_extension) );
+ return ext_imap4flags_stringlist_create_single
+ (renv, _get_flags_string(flg_ext, renv->result), FALSE);
+ }
+
+ return ext_imap4flags_stringlist_create(renv, flags_list, TRUE);
+}
+
+void ext_imap4flags_get_implicit_flags_init
+(struct ext_imap4flags_iter *iter, const struct sieve_extension *this_ext,
+ struct sieve_result *result)
+{
+ string_t *cur_flags = _get_flags_string(this_ext, result);
+
+ ext_imap4flags_iter_init(iter, cur_flags);
+}
+
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
new file mode 100644
index 0000000..4bedb85
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags-common.h
@@ -0,0 +1,97 @@
+#ifndef EXT_IMAP4FLAGS_COMMON_H
+#define EXT_IMAP4FLAGS_COMMON_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ext-variables.h"
+
+#include "sieve-ext-imap4flags.h"
+
+/*
+ * Side effect
+ */
+
+extern const struct sieve_side_effect_def flags_side_effect;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def flags_side_effect_operand;
+
+/*
+ * Operations
+ */
+
+enum ext_imap4flags_opcode {
+ EXT_IMAP4FLAGS_OPERATION_SETFLAG,
+ EXT_IMAP4FLAGS_OPERATION_ADDFLAG,
+ EXT_IMAP4FLAGS_OPERATION_REMOVEFLAG,
+ EXT_IMAP4FLAGS_OPERATION_HASFLAG
+};
+
+extern const struct sieve_operation_def setflag_operation;
+extern const struct sieve_operation_def addflag_operation;
+extern const struct sieve_operation_def removeflag_operation;
+extern const struct sieve_operation_def hasflag_operation;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_setflag;
+extern const struct sieve_command_def cmd_addflag;
+extern const struct sieve_command_def cmd_removeflag;
+
+extern const struct sieve_command_def tst_hasflag;
+
+/*
+ * Common command functions
+ */
+
+bool ext_imap4flags_command_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/*
+ * Flags tagged argument
+ */
+
+void ext_imap4flags_attach_flags_tag
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const char *command);
+
+/*
+ * Flag management
+ */
+
+struct ext_imap4flags_iter {
+ string_t *flags_list;
+ unsigned int offset;
+ unsigned int last;
+};
+
+void ext_imap4flags_iter_init
+ (struct ext_imap4flags_iter *iter, string_t *flags_list);
+
+const char *ext_imap4flags_iter_get_flag
+ (struct ext_imap4flags_iter *iter);
+
+/* Flag operations */
+
+typedef int (*ext_imapflag_flag_operation_t)
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index, struct sieve_stringlist *flags)
+ ATTR_NULL(2);
+
+/* Flags access */
+
+void ext_imap4flags_get_implicit_flags_init
+ (struct ext_imap4flags_iter *iter, const struct sieve_extension *this_ext,
+ struct sieve_result *result);
+
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags.c
new file mode 100644
index 0000000..23230c1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imap4flags.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension imap4flags
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5232
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *imap4flags_operations[] = {
+ &setflag_operation,
+ &addflag_operation,
+ &removeflag_operation,
+ &hasflag_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_imap4flags_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_imap4flags_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_extension_def imap4flags_extension = {
+ .name = "imap4flags",
+ .version = 1,
+ .validator_load = ext_imap4flags_validator_load,
+ .interpreter_load = ext_imap4flags_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(imap4flags_operations),
+ SIEVE_EXT_DEFINE_OPERAND(flags_side_effect_operand)
+};
+
+static bool ext_imap4flags_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_setflag);
+ sieve_validator_register_command(valdtr, ext, &cmd_addflag);
+ sieve_validator_register_command(valdtr, ext, &cmd_removeflag);
+ sieve_validator_register_command(valdtr, ext, &tst_hasflag);
+
+ /* Attach :flags tag to keep and fileinto commands */
+ ext_imap4flags_attach_flags_tag(valdtr, ext, "keep");
+ ext_imap4flags_attach_flags_tag(valdtr, ext, "fileinto");
+
+ /* Attach flags side-effect to keep and fileinto actions */
+ sieve_ext_imap4flags_register_side_effect(valdtr, ext, "keep");
+ sieve_ext_imap4flags_register_side_effect(valdtr, ext, "fileinto");
+
+ return TRUE;
+}
+
+void sieve_ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv)
+{
+ sieve_interpreter_extension_register
+ (renv->interp, ext, &imap4flags_interpreter_extension, NULL);
+}
+
+static bool ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_ext_imap4flags_interpreter_load(ext, renv);
+ return TRUE;
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imapflags.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imapflags.c
new file mode 100644
index 0000000..ba99035
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/ext-imapflags.c
@@ -0,0 +1,213 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension imapflags
+ * --------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-melnikov-sieve-imapflags-03.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_mark_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/* Mark command
+ *
+ * Syntax:
+ * mark
+ */
+
+static const struct sieve_command_def cmd_mark = {
+ .identifier = "mark",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_mark_validate
+};
+
+/* Unmark command
+ *
+ * Syntax:
+ * unmark
+ */
+static const struct sieve_command_def cmd_unmark = {
+ .identifier = "unmark",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_mark_validate
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_imapflags_load
+ (const struct sieve_extension *ext, void **context);
+static bool ext_imapflags_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_imapflags_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_extension_def imapflags_extension = {
+ .name = "imapflags",
+ .load = ext_imapflags_load,
+ .validator_load = ext_imapflags_validator_load,
+ .interpreter_load = ext_imapflags_interpreter_load
+};
+
+static bool ext_imapflags_load
+(const struct sieve_extension *ext, void **context)
+{
+ if ( *context == NULL ) {
+ /* Make sure real extension is registered, it is needed by the binary */
+ *context = (void *)
+ sieve_extension_require(ext->svinst, &imap4flags_extension, FALSE);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_imapflags_validator_check_conflict
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *other_ext,
+ bool required);
+static bool ext_imapflags_validator_validate
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+
+const struct sieve_validator_extension
+imapflags_validator_extension = {
+ .ext = &imapflags_extension,
+ .check_conflict = ext_imapflags_validator_check_conflict,
+ .validate = ext_imapflags_validator_validate
+};
+
+static bool ext_imapflags_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_validator_extension_register
+ (valdtr, ext, &imapflags_validator_extension, NULL);
+
+ return TRUE;
+}
+
+static bool ext_imapflags_validator_check_conflict
+(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required ATTR_UNUSED)
+{
+ const struct sieve_extension *master_ext =
+ (const struct sieve_extension *) ext->context;
+
+ if ( ext_other == master_ext ) {
+ sieve_argument_validate_error(valdtr, require_arg,
+ "the (deprecated) imapflags extension cannot be used "
+ "together with the imap4flags extension");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool ext_imapflags_validator_validate
+(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg ATTR_UNUSED,
+ bool required ATTR_UNUSED)
+{
+ const struct sieve_extension *master_ext =
+ (const struct sieve_extension *) ext->context;
+
+ /* No conflicts */
+
+ /* Register commands */
+ sieve_validator_register_command(valdtr, master_ext, &cmd_setflag);
+ sieve_validator_register_command(valdtr, master_ext, &cmd_addflag);
+ sieve_validator_register_command(valdtr, master_ext, &cmd_removeflag);
+
+ sieve_validator_register_command(valdtr, master_ext, &cmd_mark);
+ sieve_validator_register_command(valdtr, master_ext, &cmd_unmark);
+
+ /* Attach flags side-effect to keep and fileinto actions */
+ sieve_ext_imap4flags_register_side_effect(valdtr, master_ext, "keep");
+ sieve_ext_imap4flags_register_side_effect(valdtr, master_ext, "fileinto");
+
+ return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static bool ext_imapflags_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ const struct sieve_extension *master_ext =
+ (const struct sieve_extension *) ext->context;
+
+ sieve_ext_imap4flags_interpreter_load(master_ext, renv);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_mark_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ if ( sieve_command_is(cmd, cmd_mark) )
+ cmd->def = &cmd_addflag;
+ else
+ cmd->def = &cmd_removeflag;
+
+ cmd->first_positional = sieve_ast_argument_cstring_create
+ (cmd->ast_node, "\\flagged", cmd->ast_node->source_line);
+
+ if ( !sieve_validator_argument_activate
+ (valdtr, cmd, cmd->first_positional, FALSE) )
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/sieve-ext-imap4flags.h b/pigeonhole/src/lib-sieve/plugins/imap4flags/sieve-ext-imap4flags.h
new file mode 100644
index 0000000..b38a3c8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/sieve-ext-imap4flags.h
@@ -0,0 +1,74 @@
+#ifndef SIEVE_EXT_IMAP4FLAGS_H
+#define SIEVE_EXT_IMAP4FLAGS_H
+
+struct sieve_variable_storage;
+
+/*
+ * Imap4flags extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def imap4flags_extension;
+extern const struct sieve_interpreter_extension
+ imap4flags_interpreter_extension;
+
+static inline const struct sieve_extension *
+sieve_ext_imap4flags_require_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_require
+ (svinst, &imap4flags_extension, TRUE);
+}
+
+void sieve_ext_imap4flags_interpreter_load
+(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv);
+
+/*
+ * Action side-effect
+ */
+
+void sieve_ext_imap4flags_register_side_effect
+(struct sieve_validator *valdtr, const struct sieve_extension *flg_ext,
+ const char *command);
+
+/*
+ * Flag syntax
+ */
+
+bool sieve_ext_imap4flags_flag_is_valid(const char *flag);
+
+/*
+ * Flag manipulation
+ */
+
+int sieve_ext_imap4flags_set_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags) ATTR_NULL(3);
+int sieve_ext_imap4flags_add_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags) ATTR_NULL(3);
+int sieve_ext_imap4flags_remove_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_variable_storage *storage,
+ unsigned int var_index,
+ struct sieve_stringlist *flags) ATTR_NULL(3);
+
+/*
+ * Flag retrieval
+ */
+
+struct sieve_stringlist *sieve_ext_imap4flags_get_flags
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *flg_ext,
+ struct sieve_stringlist *flags_list);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/tag-flags.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/tag-flags.c
new file mode 100644
index 0000000..331063d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/tag-flags.c
@@ -0,0 +1,402 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "array.h"
+#include "mail-storage.h"
+
+#include "sieve-code.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-dump.h"
+
+#include "ext-imap4flags-common.h"
+
+#include <ctype.h>
+
+/*
+ * Flags tagged argument
+ */
+
+static bool
+tag_flags_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd);
+static bool
+tag_flags_validate_persistent(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ const struct sieve_extension *ext);
+static bool
+tag_flags_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd);
+
+const struct sieve_argument_def tag_flags = {
+ .identifier = "flags",
+ .validate = tag_flags_validate,
+ .generate = tag_flags_generate,
+};
+
+const struct sieve_argument_def tag_flags_implicit = {
+ .identifier = "flags-implicit",
+ .validate_persistent = tag_flags_validate_persistent,
+ .generate = tag_flags_generate,
+};
+
+/*
+ * Side effect
+ */
+
+static bool
+seff_flags_dump_context(const struct sieve_side_effect *seffect,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+seff_flags_read_context(const struct sieve_side_effect *seffect,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **context);
+
+static int
+seff_flags_merge(const struct sieve_runtime_env *renv,
+ const struct sieve_action *action,
+ const struct sieve_side_effect *old_seffect,
+ const struct sieve_side_effect *new_seffect,
+ void **old_context);
+
+static void
+seff_flags_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+seff_flags_pre_execute(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void **se_tr_context);
+
+const struct sieve_side_effect_def flags_side_effect = {
+ SIEVE_OBJECT("flags", &flags_side_effect_operand, 0),
+ .to_action = &act_store,
+ .dump_context = seff_flags_dump_context,
+ .read_context = seff_flags_read_context,
+ .merge = seff_flags_merge,
+ .print = seff_flags_print,
+ .pre_execute = seff_flags_pre_execute,
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+ SIEVE_EXT_DEFINE_SIDE_EFFECT(flags_side_effect);
+
+const struct sieve_operand_def flags_side_effect_operand = {
+ .name = "flags operand",
+ .ext_def = &imap4flags_extension,
+ .class = &sieve_side_effect_operand_class,
+ .interface = &ext_side_effects,
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_flags_validate_persistent(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd,
+ const struct sieve_extension *ext)
+{
+ if (sieve_command_find_argument(cmd, &tag_flags) == NULL)
+ sieve_command_add_dynamic_tag(cmd, ext, &tag_flags_implicit,
+ -1);
+ return TRUE;
+}
+
+static bool
+tag_flags_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* Check syntax:
+ * :flags <list-of-flags: string-list>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING_LIST, FALSE))
+ return FALSE;
+
+ tag->parameters = *arg;
+
+ /* Detach parameter */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_flags_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *param;
+
+ if (sieve_ast_argument_type(arg) != SAAT_TAG)
+ return FALSE;
+
+ sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
+ &flags_side_effect);
+
+ if (sieve_argument_is(arg, tag_flags)) {
+ /* Explicit :flags tag */
+ param = arg->parameters;
+
+ /* Call the generation function for the argument */
+ if (param->argument != NULL && param->argument->def != NULL &&
+ param->argument->def->generate != NULL &&
+ !param->argument->def->generate(cgenv, param, cmd))
+ return FALSE;
+ } else if (sieve_argument_is(arg, tag_flags_implicit)) {
+ /* Implicit flags */
+ sieve_opr_omitted_emit(cgenv->sblock);
+ } else {
+ /* Something else?! */
+ i_unreached();
+ }
+ return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+/* Context data */
+
+struct seff_flags_context {
+ ARRAY(const char *) keywords;
+ enum mail_flags flags;
+};
+
+/* Context coding */
+
+static bool
+seff_flags_dump_context(const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ return sieve_opr_stringlist_dump_ex(denv, address, "flags", "INTERNAL");
+}
+
+static struct seff_flags_context *
+seff_flags_get_implicit_context(const struct sieve_extension *this_ext,
+ struct sieve_result *result)
+{
+ pool_t pool = sieve_result_pool(result);
+ struct seff_flags_context *ctx;
+ const char *flag;
+ struct ext_imap4flags_iter flit;
+
+ ctx = p_new(pool, struct seff_flags_context, 1);
+ p_array_init(&ctx->keywords, pool, 2);
+
+ T_BEGIN {
+ /* Unpack */
+ ext_imap4flags_get_implicit_flags_init(&flit, this_ext, result);
+ while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
+ if (flag != NULL && *flag != '\\') {
+ /* keyword */
+ const char *keyword = p_strdup(pool, flag);
+ array_append(&ctx->keywords, &keyword, 1);
+ } else {
+ /* system flag */
+ if (flag == NULL ||
+ strcasecmp(flag, "\\flagged") == 0)
+ ctx->flags |= MAIL_FLAGGED;
+ else if (strcasecmp(flag, "\\answered") == 0)
+ ctx->flags |= MAIL_ANSWERED;
+ else if (strcasecmp(flag, "\\deleted") == 0)
+ ctx->flags |= MAIL_DELETED;
+ else if (strcasecmp(flag, "\\seen") == 0)
+ ctx->flags |= MAIL_SEEN;
+ else if (strcasecmp(flag, "\\draft") == 0)
+ ctx->flags |= MAIL_DRAFT;
+ }
+ }
+ } T_END;
+
+ return ctx;
+}
+
+static int
+seff_flags_do_read_context(const struct sieve_side_effect *seffect,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **se_context)
+{
+ pool_t pool = sieve_result_pool(renv->result);
+ struct seff_flags_context *ctx;
+ string_t *flags_item;
+ struct sieve_stringlist *flag_list = NULL;
+ int ret;
+
+ ret = sieve_opr_stringlist_read_ex(renv, address, "flags", TRUE,
+ &flag_list);
+ if (ret <= 0)
+ return ret;
+
+ if (flag_list == NULL) {
+ /* Flag list is omitted, use current value of internal
+ * variable to construct side effect context.
+ */
+ *se_context = seff_flags_get_implicit_context(
+ SIEVE_OBJECT_EXTENSION(seffect), renv->result);
+ return SIEVE_EXEC_OK;
+ }
+
+ ctx = p_new(pool, struct seff_flags_context, 1);
+ p_array_init(&ctx->keywords, pool, 2);
+
+ /* Unpack */
+ flags_item = NULL;
+ while ((ret = sieve_stringlist_next_item(flag_list, &flags_item)) > 0) {
+ const char *flag;
+ struct ext_imap4flags_iter flit;
+
+ ext_imap4flags_iter_init(&flit, flags_item);
+
+ while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
+ if (flag != NULL && *flag != '\\') {
+ /* keyword */
+ const char *keyword = p_strdup(pool, flag);
+
+ /* FIXME: should check for duplicates (cannot
+ trust variables) */
+ array_append(&ctx->keywords, &keyword, 1);
+ } else {
+ /* system flag */
+ if (flag == NULL ||
+ strcasecmp(flag, "\\flagged") == 0)
+ ctx->flags |= MAIL_FLAGGED;
+ else if (strcasecmp(flag, "\\answered") == 0)
+ ctx->flags |= MAIL_ANSWERED;
+ else if (strcasecmp(flag, "\\deleted") == 0)
+ ctx->flags |= MAIL_DELETED;
+ else if (strcasecmp(flag, "\\seen") == 0)
+ ctx->flags |= MAIL_SEEN;
+ else if (strcasecmp(flag, "\\draft") == 0)
+ ctx->flags |= MAIL_DRAFT;
+ }
+ }
+ }
+
+ if (ret < 0)
+ return flag_list->exec_status;
+
+ *se_context = (void *)ctx;
+ return SIEVE_EXEC_OK;
+}
+
+static int
+seff_flags_read_context(const struct sieve_side_effect *seffect,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **se_context)
+{
+ int ret;
+
+ T_BEGIN {
+ ret = seff_flags_do_read_context(seffect, renv, address,
+ se_context);
+ } T_END;
+
+ return ret;
+}
+
+/* Result verification */
+
+static int
+seff_flags_merge(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_side_effect *old_seffect ATTR_UNUSED,
+ const struct sieve_side_effect *new_seffect,
+ void **old_context)
+{
+ if (new_seffect != NULL)
+ *old_context = new_seffect->context;
+ return 1;
+}
+
+/* Result printing */
+
+static void
+seff_flags_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ struct sieve_result *result = rpenv->result;
+ struct seff_flags_context *ctx =
+ (struct seff_flags_context *)seffect->context;
+ unsigned int i;
+
+ if (ctx == NULL) {
+ ctx = seff_flags_get_implicit_context(
+ SIEVE_OBJECT_EXTENSION(seffect), result);
+ }
+
+ if (ctx->flags != 0 || array_count(&ctx->keywords) > 0) {
+ T_BEGIN {
+ string_t *flags = t_str_new(128);
+
+ if ((ctx->flags & MAIL_FLAGGED) > 0)
+ str_printfa(flags, " \\flagged");
+ if ((ctx->flags & MAIL_ANSWERED) > 0)
+ str_printfa(flags, " \\answered");
+ if ((ctx->flags & MAIL_DELETED) > 0)
+ str_printfa(flags, " \\deleted");
+ if ((ctx->flags & MAIL_SEEN) > 0)
+ str_printfa(flags, " \\seen");
+ if ((ctx->flags & MAIL_DRAFT) > 0)
+ str_printfa(flags, " \\draft");
+
+ for (i = 0; i < array_count(&ctx->keywords); i++) {
+ const char *const *keyword =
+ array_idx(&ctx->keywords, i);
+ str_printfa(flags, " %s",
+ str_sanitize(*keyword, 64));
+ }
+
+ sieve_result_seffect_printf(rpenv, "add IMAP flags:%s",
+ str_c(flags));
+ } T_END;
+ }
+}
+
+/* Result execution */
+
+static int
+seff_flags_pre_execute(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void **se_tr_context ATTR_UNUSED)
+{
+ struct seff_flags_context *ctx = seffect->context;
+ const char *const *keywords;
+
+ if (ctx == NULL) {
+ ctx = seff_flags_get_implicit_context(
+ SIEVE_OBJECT_EXTENSION(seffect), aenv->result);
+ }
+
+ (void)array_append_space(&ctx->keywords);
+ keywords = array_idx(&ctx->keywords, 0);
+
+ sieve_act_store_add_flags(aenv, tr_context, keywords, ctx->flags);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/imap4flags/tst-hasflag.c b/pigeonhole/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
new file mode 100644
index 0000000..23f2acc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/imap4flags/tst-hasflag.c
@@ -0,0 +1,248 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-imap4flags-common.h"
+
+/*
+ * Hasflag test
+ *
+ * Syntax:
+ * hasflag [MATCH-TYPE] [COMPARATOR] [<variable-list: string-list>]
+ * <list-of-flags: string-list>
+ */
+
+static bool tst_hasflag_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_hasflag_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_hasflag_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_hasflag = {
+ .identifier = "hasflag",
+ .type = SCT_TEST,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_hasflag_registered,
+ .validate = tst_hasflag_validate,
+ .generate = tst_hasflag_generate
+};
+
+/*
+ * Hasflag operation
+ */
+
+static bool tst_hasflag_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_hasflag_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def hasflag_operation = {
+ .mnemonic = "HASFLAG",
+ .ext_def = &imap4flags_extension,
+ .code = EXT_IMAP4FLAGS_OPERATION_HASFLAG,
+ .dump = tst_hasflag_operation_dump,
+ .execute = tst_hasflag_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_hasflag_optional {
+ OPT_VARIABLES = SIEVE_MATCH_OPT_LAST,
+};
+
+/*
+ * Tag registration
+ */
+
+static bool tst_hasflag_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_hasflag_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *vars = tst->first_positional;
+ struct sieve_ast_argument *keys = sieve_ast_argument_next(vars);
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ if ( !ext_imap4flags_command_validate(valdtr, tst) )
+ return FALSE;
+
+ if ( keys == NULL ) {
+ keys = vars;
+ vars = NULL;
+ } else {
+ vars->argument->id_code = OPT_VARIABLES;
+ }
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, keys, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_hasflag_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &hasflag_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_hasflag_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "HASFLAG");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+
+ for (;;) {
+ bool opok = TRUE;
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+ < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_VARIABLES:
+ opok = sieve_opr_stringlist_dump(denv, address, "variables");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if ( !opok ) return FALSE;
+ }
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "list of flags");
+}
+
+/*
+ * Interpretation
+ */
+
+static int tst_hasflag_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *op = renv->oprtn;
+ int opt_code = 0;
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_stringlist *flag_list, *variables_list, *value_list, *key_list;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ variables_list = NULL;
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_VARIABLES:
+ ret = sieve_opr_stringlist_read
+ (renv, address, "variables-list", &variables_list);
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( ret <= 0 ) return ret;
+ }
+
+ /* Fixed operands */
+
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "flag-list", &flag_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "hasflag test");
+
+ value_list = sieve_ext_imap4flags_get_flags
+ (renv, op->ext, variables_list);
+
+ if ( sieve_match_type_is(&mcht, is_match_type) ||
+ sieve_match_type_is(&mcht, contains_match_type) ) {
+ key_list = sieve_ext_imap4flags_get_flags
+ (renv, op->ext, flag_list);
+ } else {
+ key_list = flag_list;
+ }
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/Makefile.am b/pigeonhole/src/lib-sieve/plugins/include/Makefile.am
new file mode 100644
index 0000000..5ce1599
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_include.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-include.c \
+ cmd-return.c \
+ cmd-global.c
+
+libsieve_ext_include_la_SOURCES = \
+ $(cmds) \
+ ext-include-common.c \
+ ext-include-binary.c \
+ ext-include-variables.c \
+ ext-include.c
+
+noinst_HEADERS = \
+ ext-include-common.h \
+ ext-include-limits.h \
+ ext-include-binary.h \
+ ext-include-variables.h
diff --git a/pigeonhole/src/lib-sieve/plugins/include/Makefile.in b/pigeonhole/src/lib-sieve/plugins/include/Makefile.in
new file mode 100644
index 0000000..06f003f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/Makefile.in
@@ -0,0 +1,718 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/include
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_include_la_LIBADD =
+am__objects_1 = cmd-include.lo cmd-return.lo cmd-global.lo
+am_libsieve_ext_include_la_OBJECTS = $(am__objects_1) \
+ ext-include-common.lo ext-include-binary.lo \
+ ext-include-variables.lo ext-include.lo
+libsieve_ext_include_la_OBJECTS = \
+ $(am_libsieve_ext_include_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-global.Plo \
+ ./$(DEPDIR)/cmd-include.Plo ./$(DEPDIR)/cmd-return.Plo \
+ ./$(DEPDIR)/ext-include-binary.Plo \
+ ./$(DEPDIR)/ext-include-common.Plo \
+ ./$(DEPDIR)/ext-include-variables.Plo \
+ ./$(DEPDIR)/ext-include.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_include_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_include_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_include.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-include.c \
+ cmd-return.c \
+ cmd-global.c
+
+libsieve_ext_include_la_SOURCES = \
+ $(cmds) \
+ ext-include-common.c \
+ ext-include-binary.c \
+ ext-include-variables.c \
+ ext-include.c
+
+noinst_HEADERS = \
+ ext-include-common.h \
+ ext-include-limits.h \
+ ext-include-binary.h \
+ ext-include-variables.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/include/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/include/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_include.la: $(libsieve_ext_include_la_OBJECTS) $(libsieve_ext_include_la_DEPENDENCIES) $(EXTRA_libsieve_ext_include_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_include_la_OBJECTS) $(libsieve_ext_include_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-global.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-include.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-return.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-include-binary.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-include-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-include-variables.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-include.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-global.Plo
+ -rm -f ./$(DEPDIR)/cmd-include.Plo
+ -rm -f ./$(DEPDIR)/cmd-return.Plo
+ -rm -f ./$(DEPDIR)/ext-include-binary.Plo
+ -rm -f ./$(DEPDIR)/ext-include-common.Plo
+ -rm -f ./$(DEPDIR)/ext-include-variables.Plo
+ -rm -f ./$(DEPDIR)/ext-include.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-global.Plo
+ -rm -f ./$(DEPDIR)/cmd-include.Plo
+ -rm -f ./$(DEPDIR)/cmd-return.Plo
+ -rm -f ./$(DEPDIR)/ext-include-binary.Plo
+ -rm -f ./$(DEPDIR)/ext-include-common.Plo
+ -rm -f ./$(DEPDIR)/ext-include-variables.Plo
+ -rm -f ./$(DEPDIR)/ext-include.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/include/cmd-global.c b/pigeonhole/src/lib-sieve/plugins/include/cmd-global.c
new file mode 100644
index 0000000..0ef37eb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/cmd-global.c
@@ -0,0 +1,329 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_global_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_global_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_global = {
+ .identifier = "global",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_global_validate,
+ .generate = cmd_global_generate,
+};
+
+/* DEPRICATED:
+ */
+
+/* Import command
+ *
+ * Syntax
+ * import
+ */
+const struct sieve_command_def cmd_import = {
+ .identifier = "import",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_global_validate,
+ .generate = cmd_global_generate,
+};
+
+/* Export command
+ *
+ * Syntax
+ * export
+ */
+const struct sieve_command_def cmd_export = {
+ .identifier = "export",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_global_validate,
+ .generate = cmd_global_generate,
+};
+
+/*
+ * Operations
+ */
+
+static bool opc_global_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int opc_global_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Global operation */
+
+const struct sieve_operation_def global_operation = {
+ .mnemonic = "GLOBAL",
+ .ext_def = &include_extension,
+ .code = EXT_INCLUDE_OPERATION_GLOBAL,
+ .dump = opc_global_dump,
+ .execute = opc_global_execute
+};
+
+/*
+ * Validation
+ */
+
+static inline struct sieve_argument *_create_variable_argument
+(struct sieve_command *cmd, struct sieve_variable *var)
+{
+ struct sieve_argument *argument = sieve_argument_create
+ (cmd->ast_node->ast, NULL, cmd->ext, 0);
+
+ argument->data = (void *) var;
+
+ return argument;
+}
+
+static bool cmd_global_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct sieve_command *prev = sieve_command_prev(cmd);
+
+ /* DEPRECATED: Check valid command placement */
+ if ( !sieve_command_is(cmd, cmd_global) ) {
+ if ( !sieve_command_is_toplevel(cmd) ||
+ ( !sieve_command_is_first(cmd) && prev != NULL &&
+ !sieve_command_is(prev, cmd_require) &&
+ !sieve_command_is(prev, cmd_import) &&
+ !sieve_command_is(prev, cmd_export) ) ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the DEPRECATED %s command can only be placed at top level "
+ "at the beginning of the file after any require or "
+ "import/export commands",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+ }
+
+ /* Check for use of variables extension */
+ if ( !ext_include_validator_have_variables(this_ext, valdtr) ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "%s command requires that variables extension is active",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+
+ /* Register global variable */
+ if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+ /* Single string */
+ const char *identifier = sieve_ast_argument_strc(arg);
+ struct sieve_variable *var;
+
+ if ( (var=ext_include_variable_import_global
+ (valdtr, cmd, identifier)) == NULL )
+ return FALSE;
+
+ arg->argument = _create_variable_argument(cmd, var);
+
+ } else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+ /* String list */
+ struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+ while ( stritem != NULL ) {
+ const char *identifier = sieve_ast_argument_strc(stritem);
+ struct sieve_variable *var;
+
+ if ( (var=ext_include_variable_import_global
+ (valdtr, cmd, identifier)) == NULL )
+ return FALSE;
+
+ stritem->argument = _create_variable_argument(cmd, var);
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+ } else {
+ /* Something else */
+ sieve_argument_validate_error(valdtr, arg,
+ "the %s command accepts a single string or string list argument, "
+ "but %s was found", sieve_command_identifier(cmd),
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ /* Join global commands with predecessors if possible */
+ if ( sieve_commands_equal(prev, cmd) ) {
+ /* Join this command's string list with the previous one */
+ prev->first_positional = sieve_ast_stringlist_join
+ (prev->first_positional, cmd->first_positional);
+
+ if ( prev->first_positional == NULL ) {
+ /* Not going to happen unless MAXINT stringlist items are specified */
+ sieve_command_validate_error(valdtr, cmd,
+ "compiler reached AST limit (script too complex)");
+ return FALSE;
+ }
+
+ /* Detach this command node */
+ sieve_ast_node_detach(cmd->ast_node);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_global_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &global_operation);
+
+ if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+ /* Single string */
+ struct sieve_variable *var = (struct sieve_variable *) arg->argument->data;
+
+ (void)sieve_binary_emit_unsigned(cgenv->sblock, 1);
+ (void)sieve_binary_emit_unsigned(cgenv->sblock, var->index);
+
+ } else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+ /* String list */
+ struct sieve_ast_argument *stritem = sieve_ast_strlist_first(arg);
+
+ (void)sieve_binary_emit_unsigned
+ (cgenv->sblock, sieve_ast_strlist_count(arg));
+
+ while ( stritem != NULL ) {
+ struct sieve_variable *var =
+ (struct sieve_variable *) stritem->argument->data;
+
+ (void)sieve_binary_emit_unsigned(cgenv->sblock, var->index);
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+ } else {
+ i_unreached();
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool opc_global_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = denv->oprtn->ext;
+ unsigned int count, i, var_count;
+ struct sieve_variable_scope_binary *global_vars;
+ struct sieve_variable_scope *global_scope;
+ struct sieve_variable * const *vars;
+
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &count) )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "GLOBAL (count: %u):", count);
+
+ global_vars = ext_include_binary_get_global_scope(this_ext, denv->sbin);
+ global_scope = sieve_variable_scope_binary_get(global_vars);
+ vars = sieve_variable_scope_get_variables(global_scope, &var_count);
+
+ sieve_code_descend(denv);
+
+ for ( i = 0; i < count; i++ ) {
+ unsigned int index;
+
+ sieve_code_mark(denv);
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &index) ||
+ index >= var_count )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "%d: VAR[%d]: '%s'", i, index, vars[index]->identifier);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_global_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct sieve_variable_scope_binary *global_vars;
+ struct sieve_variable_scope *global_scope;
+ struct sieve_variable_storage *storage;
+ struct sieve_variable * const *vars;
+ unsigned int var_count, count, i;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &count) ) {
+ sieve_runtime_trace_error(renv,
+ "global: count operand invalid");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ global_vars = ext_include_binary_get_global_scope(this_ext, renv->sbin);
+ global_scope = sieve_variable_scope_binary_get(global_vars);
+ vars = sieve_variable_scope_get_variables(global_scope, &var_count);
+ storage = ext_include_interpreter_get_global_variables
+ (this_ext, renv->interp);
+
+ for ( i = 0; i < count; i++ ) {
+ unsigned int index;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+ sieve_runtime_trace_error(renv,
+ "global: variable index operand invalid");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( index >= var_count ) {
+ sieve_runtime_trace_error(renv,
+ "global: variable index %u is invalid in global storage (> %u)",
+ index, var_count);
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "global: exporting variable '%s' [gvid: %u, vid: %u]",
+ vars[index]->identifier, i, index);
+
+ /* Make sure variable is initialized (export) */
+ (void)sieve_variable_get_modifiable(storage, index, NULL);
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/cmd-include.c b/pigeonhole/src/lib-sieve/plugins/include/cmd-include.c
new file mode 100644
index 0000000..92aefb4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/cmd-include.c
@@ -0,0 +1,406 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+
+/*
+ * Include command
+ *
+ * Syntax:
+ * include [LOCATION] [":once"] [":optional"] <value: string>
+ *
+ * [LOCATION]:
+ * ":personal" / ":global"
+ */
+
+static bool cmd_include_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_include_pre_validate
+ (struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd);
+static bool cmd_include_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_include_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_include = {
+ .identifier = "include",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_include_registered,
+ .pre_validate = cmd_include_pre_validate,
+ .validate = cmd_include_validate,
+ .generate = cmd_include_generate
+};
+
+/*
+ * Include operation
+ */
+
+static bool opc_include_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int opc_include_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def include_operation = {
+ .mnemonic = "include",
+ .ext_def = &include_extension,
+ .code = EXT_INCLUDE_OPERATION_INCLUDE,
+ .dump = opc_include_dump,
+ .execute = opc_include_execute
+};
+
+/*
+ * Context structures
+ */
+
+struct cmd_include_context_data {
+ enum ext_include_script_location location;
+
+ struct sieve_script *script;
+ enum ext_include_flags flags;
+
+ bool location_assigned:1;
+};
+
+/*
+ * Tagged arguments
+ */
+
+static bool cmd_include_validate_location_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def include_personal_tag = {
+ .identifier = "personal",
+ .validate = cmd_include_validate_location_tag
+};
+
+static const struct sieve_argument_def include_global_tag = {
+ .identifier = "global",
+ .validate = cmd_include_validate_location_tag
+};
+
+static bool cmd_include_validate_boolean_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def include_once_tag = {
+ .identifier = "once",
+ .validate = cmd_include_validate_boolean_tag
+};
+
+static const struct sieve_argument_def include_optional_tag = {
+ .identifier = "optional",
+ .validate = cmd_include_validate_boolean_tag
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_include_validate_location_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_include_context_data *ctx_data =
+ (struct cmd_include_context_data *) cmd->data;
+
+ if ( ctx_data->location_assigned) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "include: cannot use location tags ':personal' and ':global' "
+ "multiple times");
+ return FALSE;
+ }
+
+ if ( sieve_argument_is(*arg, include_personal_tag) )
+ ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
+ else if ( sieve_argument_is(*arg, include_global_tag) )
+ ctx_data->location = EXT_INCLUDE_LOCATION_GLOBAL;
+ else
+ return FALSE;
+
+ ctx_data->location_assigned = TRUE;
+
+ /* Delete this tag (for now) */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ return TRUE;
+}
+
+static bool cmd_include_validate_boolean_tag
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_include_context_data *ctx_data =
+ (struct cmd_include_context_data *) cmd->data;
+
+ if ( sieve_argument_is(*arg, include_once_tag) )
+ ctx_data->flags |= EXT_INCLUDE_FLAG_ONCE;
+ else
+ ctx_data->flags |= EXT_INCLUDE_FLAG_OPTIONAL;
+
+ /* Delete this tag (for now) */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_include_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_personal_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_global_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_once_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext, &include_optional_tag, 0);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_include_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ struct cmd_include_context_data *ctx_data;
+
+ /* Assign context */
+ ctx_data = p_new(sieve_command_pool(cmd), struct cmd_include_context_data, 1);
+ ctx_data->location = EXT_INCLUDE_LOCATION_PERSONAL;
+ cmd->data = ctx_data;
+
+ return TRUE;
+}
+
+static bool cmd_include_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct cmd_include_context_data *ctx_data =
+ (struct cmd_include_context_data *) cmd->data;
+ struct sieve_storage *storage;
+ struct sieve_script *script;
+ const char *script_name;
+ enum sieve_error error = SIEVE_ERROR_NONE;
+ int ret;
+
+ /* Check argument */
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ /*
+ * Variables are not allowed.
+ */
+ if ( !sieve_argument_is_string_literal(arg) ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "the include command requires a constant string for its value argument");
+ return FALSE;
+ }
+
+ /* Find the script */
+
+ script_name = sieve_ast_argument_strc(arg);
+
+ if ( !sieve_script_name_is_valid(script_name) ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "include: invalid script name '%s'",
+ str_sanitize(script_name, 80));
+ return FALSE;
+ }
+
+ storage = ext_include_get_script_storage
+ (this_ext, ctx_data->location, script_name, &error);
+ if ( storage == NULL ) {
+ // FIXME: handle ':optional' in this case
+ if (error == SIEVE_ERROR_NOT_FOUND) {
+ sieve_argument_validate_error(valdtr, arg,
+ "include: %s location for included script `%s' is unavailable "
+ "(contact system administrator for more information)",
+ ext_include_script_location_name(ctx_data->location),
+ str_sanitize(script_name, 80));
+ } else {
+ sieve_argument_validate_error(valdtr, arg,
+ "include: failed to access %s location for included script `%s' "
+ "(contact system administrator for more information)",
+ ext_include_script_location_name(ctx_data->location),
+ str_sanitize(script_name, 80));
+ }
+ return FALSE;
+ }
+
+ /* Create script object */
+ script = sieve_storage_get_script
+ (storage, script_name, &error);
+ if ( script == NULL )
+ return FALSE;
+
+ ret = sieve_script_open(script, &error);
+ if ( ret < 0 ) {
+ if ( error != SIEVE_ERROR_NOT_FOUND ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "failed to access included %s script '%s': %s",
+ ext_include_script_location_name(ctx_data->location),
+ str_sanitize(script_name, 80),
+ sieve_script_get_last_error_lcase(script));
+ sieve_script_unref(&script);
+ return FALSE;
+
+ /* Not found */
+ } else {
+ enum sieve_compile_flags cpflags =
+ sieve_validator_compile_flags(valdtr);
+
+ if ( (ctx_data->flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ) {
+ /* :optional */
+
+ } else if ( (cpflags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 ) {
+ /* Script is being uploaded */
+ sieve_argument_validate_warning(valdtr, arg,
+ "included %s script '%s' does not exist (ignored during upload)",
+ ext_include_script_location_name(ctx_data->location),
+ str_sanitize(script_name, 80));
+ ctx_data->flags |= EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD;
+
+ } else {
+ /* Should have existed */
+ sieve_argument_validate_error(valdtr, arg,
+ "included %s script '%s' does not exist",
+ ext_include_script_location_name(ctx_data->location),
+ str_sanitize(script_name, 80));
+ sieve_script_unref(&script);
+ return FALSE;
+ }
+ }
+ }
+
+ ext_include_ast_link_included_script(cmd->ext, cmd->ast_node->ast, script);
+ ctx_data->script = script;
+
+ (void)sieve_ast_arguments_detach(arg, 1);
+ return TRUE;
+}
+
+/*
+ * Code Generation
+ */
+
+static bool cmd_include_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct cmd_include_context_data *ctx_data =
+ (struct cmd_include_context_data *) cmd->data;
+ const struct ext_include_script_info *included;
+ int ret;
+
+ /* Compile (if necessary) and include the script into the binary.
+ * This yields the id of the binary block containing the compiled byte code.
+ */
+ if ( (ret=ext_include_generate_include
+ (cgenv, cmd, ctx_data->location, ctx_data->flags, ctx_data->script,
+ &included)) < 0 )
+ return FALSE;
+
+ if ( ret > 0 ) {
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &include_operation);
+ (void)sieve_binary_emit_unsigned(cgenv->sblock, included->id);
+ (void)sieve_binary_emit_byte(cgenv->sblock, ctx_data->flags);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool opc_include_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ const struct ext_include_script_info *included;
+ struct ext_include_binary_context *binctx;
+ unsigned int include_id, flags;
+
+ sieve_code_dumpf(denv, "INCLUDE:");
+
+ sieve_code_mark(denv);
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &include_id) )
+ return FALSE;
+
+ if ( !sieve_binary_read_byte(denv->sblock, address, &flags) )
+ return FALSE;
+
+ binctx = ext_include_binary_get_context(denv->oprtn->ext, denv->sbin);
+ included = ext_include_binary_script_get_included(binctx, include_id);
+ if ( included == NULL )
+ return FALSE;
+
+ sieve_code_descend(denv);
+ sieve_code_dumpf(denv, "script: `%s' from %s %s%s[ID: %d, BLOCK: %d]",
+ sieve_script_name(included->script), sieve_script_location(included->script),
+ ((flags & EXT_INCLUDE_FLAG_ONCE) != 0 ? "(once) " : ""),
+ ((flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0 ? "(optional) " : ""),
+ include_id, sieve_binary_block_get_id(included->block));
+
+ return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_include_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ unsigned int include_id, flags;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &include_id) ) {
+ sieve_runtime_trace_error(renv, "invalid include-id operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &flags) ) {
+ sieve_runtime_trace_error(renv, "invalid flags operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ return ext_include_execute_include
+ (renv, include_id, (enum ext_include_flags)flags);
+}
+
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/cmd-return.c b/pigeonhole/src/lib-sieve/plugins/include/cmd-return.c
new file mode 100644
index 0000000..65a156d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/cmd-return.c
@@ -0,0 +1,71 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-include-common.h"
+
+/*
+ * Return command
+ *
+ * Syntax
+ * return
+ */
+
+static bool cmd_return_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_return = {
+ .identifier = "return",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_return_generate
+};
+
+/*
+ * Return operation
+ */
+
+static int opc_return_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def return_operation = {
+ .mnemonic = "RETURN",
+ .ext_def = &include_extension,
+ .code = EXT_INCLUDE_OPERATION_RETURN,
+ .execute = opc_return_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool cmd_return_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &return_operation);
+
+ return TRUE;
+}
+
+/*
+ * Execution
+ */
+
+static int opc_return_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ ext_include_execute_return(renv);
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.c b/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.c
new file mode 100644
index 0000000..3711c30
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.c
@@ -0,0 +1,492 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-limits.h"
+#include "ext-include-variables.h"
+#include "ext-include-binary.h"
+
+/*
+ * Forward declarations
+ */
+
+static bool ext_include_binary_pre_save
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context, enum sieve_error *error_r);
+static bool ext_include_binary_open
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context);
+static bool ext_include_binary_up_to_date
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context, enum sieve_compile_flags cpflags);
+static void ext_include_binary_free
+ (const struct sieve_extension *ext, struct sieve_binary *sbin,
+ void *context);
+
+/*
+ * Binary include extension
+ */
+
+const struct sieve_binary_extension include_binary_ext = {
+ .extension = &include_extension,
+ .binary_pre_save = ext_include_binary_pre_save,
+ .binary_open = ext_include_binary_open,
+ .binary_free = ext_include_binary_free,
+ .binary_up_to_date = ext_include_binary_up_to_date
+};
+
+/*
+ * Binary context management
+ */
+
+struct ext_include_binary_context {
+ struct sieve_binary *binary;
+ struct sieve_binary_block *dependency_block;
+
+ HASH_TABLE(struct sieve_script *,
+ struct ext_include_script_info *) included_scripts;
+ ARRAY(struct ext_include_script_info *) include_index;
+
+ struct sieve_variable_scope_binary *global_vars;
+
+ bool outdated:1;
+};
+
+static struct ext_include_binary_context *ext_include_binary_create_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+ pool_t pool = sieve_binary_pool(sbin);
+
+ struct ext_include_binary_context *ctx =
+ p_new(pool, struct ext_include_binary_context, 1);
+
+ ctx->binary = sbin;
+ hash_table_create(&ctx->included_scripts, pool, 0,
+ sieve_script_hash, sieve_script_cmp);
+ p_array_init(&ctx->include_index, pool, 128);
+
+ sieve_binary_extension_set(sbin, this_ext, &include_binary_ext, ctx);
+
+ return ctx;
+}
+
+struct ext_include_binary_context *ext_include_binary_get_context
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+ struct ext_include_binary_context *ctx = (struct ext_include_binary_context *)
+ sieve_binary_extension_get_context(sbin, this_ext);
+
+ if ( ctx == NULL )
+ ctx = ext_include_binary_create_context(this_ext, sbin);
+
+ return ctx;
+}
+
+struct ext_include_binary_context *ext_include_binary_init
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+ struct sieve_ast *ast)
+{
+ struct ext_include_ast_context *ast_ctx =
+ ext_include_get_ast_context(this_ext, ast);
+ struct ext_include_binary_context *ctx;
+
+ /* Get/create our context from the binary we are working on */
+ ctx = ext_include_binary_get_context(this_ext, sbin);
+
+ /* Create dependency block */
+ if ( ctx->dependency_block == 0 )
+ ctx->dependency_block =
+ sieve_binary_extension_create_block(sbin, this_ext);
+
+ if ( ctx->global_vars == NULL ) {
+ ctx->global_vars =
+ sieve_variable_scope_binary_create(ast_ctx->global_vars);
+ sieve_variable_scope_binary_ref(ctx->global_vars);
+ }
+
+ return ctx;
+}
+
+/*
+ * Script inclusion
+ */
+
+struct ext_include_script_info *ext_include_binary_script_include
+(struct ext_include_binary_context *binctx,
+ enum ext_include_script_location location, enum ext_include_flags flags,
+ struct sieve_script *script, struct sieve_binary_block *inc_block)
+{
+ pool_t pool = sieve_binary_pool(binctx->binary);
+ struct ext_include_script_info *incscript;
+
+ incscript = p_new(pool, struct ext_include_script_info, 1);
+ incscript->id = array_count(&binctx->include_index)+1;
+ incscript->location = location;
+ incscript->flags = flags;
+ incscript->script = script;
+ incscript->block = inc_block;
+
+ /* Unreferenced on binary_free */
+ sieve_script_ref(script);
+
+ hash_table_insert(binctx->included_scripts, script, incscript);
+ array_append(&binctx->include_index, &incscript, 1);
+
+ return incscript;
+}
+
+struct ext_include_script_info *ext_include_binary_script_get_include_info
+(struct ext_include_binary_context *binctx, struct sieve_script *script)
+{
+ struct ext_include_script_info *incscript =
+ hash_table_lookup(binctx->included_scripts, script);
+
+ return incscript;
+}
+
+const struct ext_include_script_info *ext_include_binary_script_get_included
+(struct ext_include_binary_context *binctx, unsigned int include_id)
+{
+ if ( include_id > 0 &&
+ (include_id - 1) < array_count(&binctx->include_index) ) {
+ struct ext_include_script_info *const *sinfo =
+ array_idx(&binctx->include_index, include_id - 1);
+
+ return *sinfo;
+ }
+
+ return NULL;
+}
+
+const struct ext_include_script_info *ext_include_binary_script_get
+(struct ext_include_binary_context *binctx, struct sieve_script *script)
+{
+ return hash_table_lookup(binctx->included_scripts, script);
+}
+
+unsigned int ext_include_binary_script_get_count
+(struct ext_include_binary_context *binctx)
+{
+ return array_count(&binctx->include_index);
+}
+
+/*
+ * Variables
+ */
+
+struct sieve_variable_scope_binary *ext_include_binary_get_global_scope
+(const struct sieve_extension *this_ext, struct sieve_binary *sbin)
+{
+ struct ext_include_binary_context *binctx =
+ ext_include_binary_get_context(this_ext, sbin);
+
+ return binctx->global_vars;
+}
+
+/*
+ * Binary extension
+ */
+
+static bool ext_include_binary_pre_save
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_binary *sbin ATTR_UNUSED, void *context,
+ enum sieve_error *error_r)
+{
+ struct ext_include_binary_context *binctx =
+ (struct ext_include_binary_context *) context;
+ struct ext_include_script_info *const *scripts;
+ struct sieve_binary_block *sblock = binctx->dependency_block;
+ unsigned int script_count, i;
+ bool result = TRUE;
+
+ sieve_binary_block_clear(sblock);
+
+ scripts = array_get(&binctx->include_index, &script_count);
+
+ sieve_binary_emit_unsigned(sblock, script_count);
+
+ for ( i = 0; i < script_count; i++ ) {
+ struct ext_include_script_info *incscript = scripts[i];
+
+ if ( incscript->block != NULL ) {
+ sieve_binary_emit_unsigned
+ (sblock, sieve_binary_block_get_id(incscript->block));
+ } else {
+ sieve_binary_emit_unsigned(sblock, 0);
+ }
+ sieve_binary_emit_byte(sblock, incscript->location);
+ sieve_binary_emit_cstring(sblock, sieve_script_name(incscript->script));
+ sieve_binary_emit_byte(sblock, incscript->flags);
+ sieve_script_binary_write_metadata(incscript->script, sblock);
+ }
+
+ result = ext_include_variables_save(sblock, binctx->global_vars, error_r);
+
+ return result;
+}
+
+static bool ext_include_binary_open
+(const struct sieve_extension *ext, struct sieve_binary *sbin, void *context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_include_context *ext_ctx =
+ (struct ext_include_context *)ext->context;
+ struct ext_include_binary_context *binctx =
+ (struct ext_include_binary_context *) context;
+ struct sieve_binary_block *sblock;
+ unsigned int depcount, i, block_id;
+ sieve_size_t offset;
+
+ sblock = sieve_binary_extension_get_block(sbin, ext);
+ block_id = sieve_binary_block_get_id(sblock);
+
+ offset = 0;
+
+ if ( !sieve_binary_read_unsigned(sblock, &offset, &depcount) ) {
+ e_error(svinst->event,
+ "include: failed to read include count "
+ "for dependency block %d of binary %s", block_id,
+ sieve_binary_path(sbin));
+ return FALSE;
+ }
+
+ /* Check include limit */
+ if ( depcount > ext_ctx->max_includes ) {
+ e_error(svinst->event,
+ "include: binary %s includes too many scripts (%u > %u)",
+ sieve_binary_path(sbin), depcount, ext_ctx->max_includes);
+ return FALSE;
+ }
+
+ /* Read dependencies */
+ for ( i = 0; i < depcount; i++ ) {
+ unsigned int inc_block_id;
+ struct sieve_binary_block *inc_block = NULL;
+ unsigned int location, flags;
+ string_t *script_name;
+ struct sieve_storage *storage;
+ struct sieve_script *script;
+ enum sieve_error error;
+ int ret;
+
+ if (
+ !sieve_binary_read_unsigned(sblock, &offset, &inc_block_id) ||
+ !sieve_binary_read_byte(sblock, &offset, &location) ||
+ !sieve_binary_read_string(sblock, &offset, &script_name) ||
+ !sieve_binary_read_byte(sblock, &offset, &flags) ) {
+ /* Binary is corrupt, recompile */
+ e_error(svinst->event,
+ "include: failed to read included script "
+ "from dependency block %d of binary %s",
+ block_id, sieve_binary_path(sbin));
+ return FALSE;
+ }
+
+ if ( inc_block_id != 0 &&
+ (inc_block=sieve_binary_block_get(sbin, inc_block_id)) == NULL ) {
+ e_error(svinst->event,
+ "include: failed to find block %d for included script "
+ "from dependency block %d of binary %s",
+ inc_block_id, block_id,
+ sieve_binary_path(sbin));
+ return FALSE;
+ }
+
+ if ( location >= EXT_INCLUDE_LOCATION_INVALID ) {
+ /* Binary is corrupt, recompile */
+ e_error(svinst->event,
+ "include: dependency block %d of binary %s "
+ "uses invalid script location (id %d)",
+ block_id, sieve_binary_path(sbin), location);
+ return FALSE;
+ }
+
+ /* Can we find the script dependency ? */
+ storage = ext_include_get_script_storage
+ (ext, location, str_c(script_name), &error);
+ if ( storage == NULL ) {
+ /* No, recompile */
+ // FIXME: handle ':optional' in this case
+ return FALSE;
+ }
+
+ /* Can we open the script dependency ? */
+ script = sieve_storage_get_script
+ (storage, str_c(script_name), &error);
+ if ( script == NULL ) {
+ /* No, recompile */
+ return FALSE;
+ }
+ if ( sieve_script_open(script, &error) < 0 ) {
+ if ( error != SIEVE_ERROR_NOT_FOUND ) {
+ /* No, recompile */
+ return FALSE;
+ }
+
+ if ( (flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0 ) {
+ /* Not supposed to be missing, recompile */
+ if ( svinst->debug ) {
+ e_debug(svinst->event, "include: "
+ "script '%s' included in binary %s is missing, "
+ "so recompile",
+ str_c(script_name),
+ sieve_binary_path(sbin));
+ }
+ return FALSE;
+ }
+
+ } else if (inc_block == NULL) {
+ /* Script exists, but it is missing from the binary, recompile no matter
+ * what.
+ */
+ if ( svinst->debug ) {
+ e_debug(svinst->event, "include: "
+ "script '%s' is missing in binary %s, but is now available, "
+ "so recompile", str_c(script_name), sieve_binary_path(sbin));
+ }
+ sieve_script_unref(&script);
+ return FALSE;
+ }
+
+ /* Can we read script metadata ? */
+ if ( (ret=sieve_script_binary_read_metadata
+ (script, sblock, &offset)) < 0 ) {
+ /* Binary is corrupt, recompile */
+ e_error(svinst->event, "include: "
+ "dependency block %d of binary %s "
+ "contains invalid script metadata for script %s",
+ block_id, sieve_binary_path(sbin),
+ sieve_script_location(script));
+ sieve_script_unref(&script);
+ return FALSE;
+ }
+
+ if ( ret == 0 )
+ binctx->outdated = TRUE;
+
+ (void)ext_include_binary_script_include
+ (binctx, location, flags, script, inc_block);
+
+ sieve_script_unref(&script);
+ }
+
+ if ( !ext_include_variables_load
+ (ext, sblock, &offset, &binctx->global_vars) )
+ return FALSE;
+
+ return TRUE;
+}
+
+static bool ext_include_binary_up_to_date
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_binary *sbin ATTR_UNUSED, void *context,
+ enum sieve_compile_flags cpflags ATTR_UNUSED)
+{
+ struct ext_include_binary_context *binctx =
+ (struct ext_include_binary_context *) context;
+
+ return !binctx->outdated;
+}
+
+static void ext_include_binary_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_binary *sbin ATTR_UNUSED, void *context)
+{
+ struct ext_include_binary_context *binctx =
+ (struct ext_include_binary_context *) context;
+ struct hash_iterate_context *hctx;
+ struct sieve_script *script;
+ struct ext_include_script_info *incscript;
+
+ /* Release references to all included script objects */
+ hctx = hash_table_iterate_init(binctx->included_scripts);
+ while ( hash_table_iterate
+ (hctx, binctx->included_scripts, &script, &incscript) )
+ sieve_script_unref(&incscript->script);
+ hash_table_iterate_deinit(&hctx);
+
+ hash_table_destroy(&binctx->included_scripts);
+
+ if ( binctx->global_vars != NULL )
+ sieve_variable_scope_binary_unref(&binctx->global_vars);
+}
+
+/*
+ * Dumping the binary
+ */
+
+bool ext_include_binary_dump
+(const struct sieve_extension *ext, struct sieve_dumptime_env *denv)
+{
+ struct sieve_binary *sbin = denv->sbin;
+ struct ext_include_binary_context *binctx =
+ ext_include_binary_get_context(ext, sbin);
+ struct hash_iterate_context *hctx;
+ struct sieve_script *script;
+ struct ext_include_script_info *incscript;
+
+ if ( !ext_include_variables_dump(denv, binctx->global_vars) )
+ return FALSE;
+
+ hctx = hash_table_iterate_init(binctx->included_scripts);
+ while ( hash_table_iterate
+ (hctx, binctx->included_scripts, &script, &incscript) ) {
+
+ if ( incscript->block == NULL ) {
+ sieve_binary_dump_sectionf(denv, "Included %s script '%s' (MISSING)",
+ ext_include_script_location_name(incscript->location),
+ sieve_script_name(incscript->script));
+
+ } else {
+ unsigned int block_id = sieve_binary_block_get_id(incscript->block);
+
+ sieve_binary_dump_sectionf(denv, "Included %s script '%s' (block: %d)",
+ ext_include_script_location_name(incscript->location),
+ sieve_script_name(incscript->script), block_id);
+
+ denv->sblock = incscript->block;
+ denv->cdumper = sieve_code_dumper_create(denv);
+
+ if ( denv->cdumper == NULL )
+ return FALSE;
+
+ sieve_code_dumper_run(denv->cdumper);
+ sieve_code_dumper_free(&(denv->cdumper));
+ }
+ }
+ hash_table_iterate_deinit(&hctx);
+
+ return TRUE;
+}
+
+bool ext_include_code_dump
+(const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ struct sieve_binary *sbin = denv->sbin;
+ struct ext_include_binary_context *binctx =
+ ext_include_binary_get_context(ext, sbin);
+ struct ext_include_context *ectx = ext_include_get_context(ext);
+
+ sieve_ext_variables_dump_set_scope
+ (ectx->var_ext, denv, ext,
+ sieve_variable_scope_binary_get(binctx->global_vars));
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.h b/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.h
new file mode 100644
index 0000000..066df2f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-binary.h
@@ -0,0 +1,64 @@
+#ifndef EXT_INCLUDE_BINARY_H
+#define EXT_INCLUDE_BINARY_H
+
+#include "sieve-common.h"
+
+/*
+ * Binary context management
+ */
+
+struct ext_include_binary_context;
+
+struct ext_include_binary_context *ext_include_binary_init
+ (const struct sieve_extension *this_ext, struct sieve_binary *sbin,
+ struct sieve_ast *ast);
+struct ext_include_binary_context *ext_include_binary_get_context
+ (const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+
+/*
+ * Variables
+ */
+
+struct sieve_variable_scope_binary *ext_include_binary_get_global_scope
+ (const struct sieve_extension *this_ext, struct sieve_binary *sbin);
+
+/*
+ * Including scripts
+ */
+
+struct ext_include_script_info {
+ unsigned int id;
+
+ struct sieve_script *script;
+ enum ext_include_flags flags;
+ enum ext_include_script_location location;
+
+ struct sieve_binary_block *block;
+};
+
+struct ext_include_script_info *ext_include_binary_script_include
+ (struct ext_include_binary_context *binctx,
+ enum ext_include_script_location location, enum ext_include_flags flags,
+ struct sieve_script *script, struct sieve_binary_block *inc_block);
+struct ext_include_script_info *ext_include_binary_script_get_include_info
+ (struct ext_include_binary_context *binctx, struct sieve_script *script);
+
+const struct ext_include_script_info *ext_include_binary_script_get_included
+ (struct ext_include_binary_context *binctx, unsigned int include_id);
+const struct ext_include_script_info *ext_include_binary_script_get
+ (struct ext_include_binary_context *binctx, struct sieve_script *script);
+unsigned int ext_include_binary_script_get_count
+ (struct ext_include_binary_context *binctx);
+
+/*
+ * Dumping the binary
+ */
+
+bool ext_include_binary_dump
+ (const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+bool ext_include_code_dump
+ (const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+ sieve_size_t *address ATTR_UNUSED);
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c b/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c
new file mode 100644
index 0000000..7af3f00
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c
@@ -0,0 +1,892 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-storage.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-include-common.h"
+#include "ext-include-limits.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+
+/*
+ * Forward declarations
+ */
+
+/* Generator context */
+
+struct ext_include_generator_context {
+ unsigned int nesting_depth;
+ struct sieve_script *script;
+ struct ext_include_generator_context *parent;
+};
+
+static inline struct ext_include_generator_context *
+ext_include_get_generator_context(const struct sieve_extension *ext_this,
+ struct sieve_generator *gentr);
+
+/* Interpreter context */
+
+struct ext_include_interpreter_global {
+ ARRAY(struct sieve_script *) included_scripts;
+
+ struct sieve_variable_scope_binary *var_scope;
+ struct sieve_variable_storage *var_storage;
+};
+
+struct ext_include_interpreter_context {
+ struct ext_include_interpreter_context *parent;
+ struct ext_include_interpreter_global *global;
+
+ struct sieve_interpreter *interp;
+ pool_t pool;
+
+ unsigned int nesting_depth;
+
+ struct sieve_script *script;
+ const struct ext_include_script_info *script_info;
+
+ const struct ext_include_script_info *include;
+ bool returned;
+};
+
+/*
+ * Extension configuration
+ */
+
+/* Extension hooks */
+
+bool ext_include_load(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_include_context *ctx;
+ const char *location;
+ unsigned long long int uint_setting;
+
+ if (*context != NULL)
+ ext_include_unload(ext);
+
+ ctx = i_new(struct ext_include_context, 1);
+
+ /* Get location for :global scripts */
+ location = sieve_setting_get(svinst, "sieve_global");
+ if (location == NULL)
+ location = sieve_setting_get(svinst, "sieve_global_dir");
+
+ if (location == NULL) {
+ e_debug(svinst->event, "include: "
+ "sieve_global is not set; "
+ "it is currently not possible to include `:global' scripts.");
+ }
+
+ ctx->global_location = i_strdup(location);
+
+ /* Get limits */
+ ctx->max_nesting_depth = EXT_INCLUDE_DEFAULT_MAX_NESTING_DEPTH;
+ ctx->max_includes = EXT_INCLUDE_DEFAULT_MAX_INCLUDES;
+
+ if (sieve_setting_get_uint_value(
+ svinst, "sieve_include_max_nesting_depth", &uint_setting))
+ ctx->max_nesting_depth = (unsigned int)uint_setting;
+ if (sieve_setting_get_uint_value(
+ svinst, "sieve_include_max_includes", &uint_setting))
+ ctx->max_includes = (unsigned int)uint_setting;
+
+ /* Extension dependencies */
+ ctx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+
+ *context = (void *)ctx;
+ return TRUE;
+}
+
+void ext_include_unload(const struct sieve_extension *ext)
+{
+ struct ext_include_context *ctx =
+ (struct ext_include_context *)ext->context;
+
+ if (ctx->global_storage != NULL)
+ sieve_storage_unref(&ctx->global_storage);
+ if (ctx->personal_storage != NULL)
+ sieve_storage_unref(&ctx->personal_storage);
+
+ i_free(ctx->global_location);
+ i_free(ctx);
+}
+
+/*
+ * Script access
+ */
+
+struct sieve_storage *
+ext_include_get_script_storage(const struct sieve_extension *ext,
+ enum ext_include_script_location location,
+ const char *script_name,
+ enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_include_context *ctx =
+ (struct ext_include_context *)ext->context;
+
+ switch (location) {
+ case EXT_INCLUDE_LOCATION_PERSONAL:
+ if (ctx->personal_storage == NULL) {
+ ctx->personal_storage =
+ sieve_storage_create_main(svinst, NULL, 0,
+ error_r);
+ }
+ return ctx->personal_storage;
+ case EXT_INCLUDE_LOCATION_GLOBAL:
+ if (ctx->global_location == NULL) {
+ e_info(svinst->event, "include: "
+ "sieve_global is unconfigured; "
+ "include of `:global' script `%s' is therefore not possible",
+ str_sanitize(script_name, 80));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ return NULL;
+ }
+ if (ctx->global_storage == NULL) {
+ ctx->global_storage = sieve_storage_create(
+ svinst, ctx->global_location, 0, error_r);
+ }
+ return ctx->global_storage;
+ default:
+ break;
+ }
+
+ i_unreached();
+ return NULL;
+}
+
+/*
+ * AST context management
+ */
+
+static void
+ext_include_ast_free(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_ast *ast ATTR_UNUSED, void *context)
+{
+ struct ext_include_ast_context *actx =
+ (struct ext_include_ast_context *)context;
+ struct sieve_script **scripts;
+ unsigned int count, i;
+
+ /* Unreference included scripts */
+ scripts = array_get_modifiable(&actx->included_scripts, &count);
+ for (i = 0; i < count; i++) {
+ sieve_script_unref(&scripts[i]);
+ }
+
+ /* Unreference variable scopes */
+ if (actx->global_vars != NULL)
+ sieve_variable_scope_unref(&actx->global_vars);
+}
+
+static const struct sieve_ast_extension include_ast_extension = {
+ &include_extension,
+ ext_include_ast_free
+};
+
+struct ext_include_ast_context *
+ext_include_create_ast_context(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast, struct sieve_ast *parent)
+{
+ struct ext_include_ast_context *actx;
+
+ pool_t pool = sieve_ast_pool(ast);
+ actx = p_new(pool, struct ext_include_ast_context, 1);
+ p_array_init(&actx->included_scripts, pool, 32);
+
+ if (parent != NULL) {
+ struct ext_include_ast_context *parent_ctx =
+ (struct ext_include_ast_context *)
+ sieve_ast_extension_get_context(parent, this_ext);
+
+ actx->global_vars = parent_ctx->global_vars;
+ i_assert(actx->global_vars != NULL);
+
+ sieve_variable_scope_ref(actx->global_vars);
+ } else {
+ struct ext_include_context *ectx =
+ ext_include_get_context(this_ext);
+
+ actx->global_vars = sieve_variable_scope_create(
+ this_ext->svinst, ectx->var_ext, this_ext);
+ }
+
+ sieve_ast_extension_register(ast, this_ext, &include_ast_extension,
+ (void *)actx);
+ return actx;
+}
+
+struct ext_include_ast_context *
+ext_include_get_ast_context(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast)
+{
+ struct ext_include_ast_context *actx =
+ (struct ext_include_ast_context *)
+ sieve_ast_extension_get_context(ast, this_ext);
+
+ if (actx != NULL)
+ return actx;
+ return ext_include_create_ast_context(this_ext, ast, NULL);
+}
+
+void ext_include_ast_link_included_script(
+ const struct sieve_extension *this_ext, struct sieve_ast *ast,
+ struct sieve_script *script)
+{
+ struct ext_include_ast_context *actx =
+ ext_include_get_ast_context(this_ext, ast);
+
+ array_append(&actx->included_scripts, &script, 1);
+}
+
+bool ext_include_validator_have_variables(
+ const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+ struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+ return sieve_ext_variables_is_active(ectx->var_ext, valdtr);
+}
+
+/*
+ * Generator context management
+ */
+
+static struct ext_include_generator_context *
+ext_include_create_generator_context(
+ struct sieve_generator *gentr,
+ struct ext_include_generator_context *parent,
+ struct sieve_script *script)
+{
+ struct ext_include_generator_context *ctx;
+
+ pool_t pool = sieve_generator_pool(gentr);
+ ctx = p_new(pool, struct ext_include_generator_context, 1);
+ ctx->parent = parent;
+ ctx->script = script;
+ if (parent == NULL)
+ ctx->nesting_depth = 0;
+ else
+ ctx->nesting_depth = parent->nesting_depth + 1;
+
+ return ctx;
+}
+
+static inline struct ext_include_generator_context *
+ext_include_get_generator_context(const struct sieve_extension *this_ext,
+ struct sieve_generator *gentr)
+{
+ return (struct ext_include_generator_context *)
+ sieve_generator_extension_get_context(gentr, this_ext);
+}
+
+static inline void
+ext_include_initialize_generator_context(
+ const struct sieve_extension *this_ext, struct sieve_generator *gentr,
+ struct ext_include_generator_context *parent,
+ struct sieve_script *script)
+{
+ sieve_generator_extension_set_context(
+ gentr, this_ext,
+ ext_include_create_generator_context(gentr, parent, script));
+}
+
+void ext_include_register_generator_context(
+ const struct sieve_extension *this_ext,
+ const struct sieve_codegen_env *cgenv)
+{
+ struct ext_include_generator_context *ctx =
+ ext_include_get_generator_context(this_ext, cgenv->gentr);
+
+ /* Initialize generator context if necessary */
+ if (ctx == NULL) {
+ ctx = ext_include_create_generator_context(
+ cgenv->gentr, NULL, cgenv->script);
+
+ sieve_generator_extension_set_context(
+ cgenv->gentr, this_ext, (void *)ctx);
+ }
+
+ /* Initialize ast context if necessary */
+ (void)ext_include_get_ast_context(this_ext, cgenv->ast);
+ (void)ext_include_binary_init(this_ext, cgenv->sbin, cgenv->ast);
+}
+
+/*
+ * Runtime initialization
+ */
+
+static int
+ext_include_runtime_init(const struct sieve_extension *this_ext,
+ const struct sieve_runtime_env *renv,
+ void *context, bool deferred ATTR_UNUSED)
+{
+ struct ext_include_interpreter_context *ctx =
+ (struct ext_include_interpreter_context *)context;
+ struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+ if (ctx->parent == NULL) {
+ ctx->global = p_new(ctx->pool,
+ struct ext_include_interpreter_global, 1);
+ p_array_init(&ctx->global->included_scripts, ctx->pool, 10);
+
+ ctx->global->var_scope =
+ ext_include_binary_get_global_scope(
+ this_ext, renv->sbin);
+ ctx->global->var_storage =
+ sieve_variable_storage_create(ectx->var_ext, ctx->pool,
+ ctx->global->var_scope);
+ } else {
+ ctx->global = ctx->parent->global;
+ }
+
+ sieve_ext_variables_runtime_set_storage(ectx->var_ext, renv, this_ext,
+ ctx->global->var_storage);
+ return SIEVE_EXEC_OK;
+}
+
+static struct sieve_interpreter_extension include_interpreter_extension = {
+ .ext_def = &include_extension,
+ .run = ext_include_runtime_init
+};
+
+/*
+ * Interpreter context management
+ */
+
+static struct ext_include_interpreter_context *
+ext_include_interpreter_context_create(
+ struct sieve_interpreter *interp,
+ struct ext_include_interpreter_context *parent,
+ struct sieve_script *script,
+ const struct ext_include_script_info *sinfo)
+{
+ struct ext_include_interpreter_context *ctx;
+
+ pool_t pool = sieve_interpreter_pool(interp);
+ ctx = p_new(pool, struct ext_include_interpreter_context, 1);
+ ctx->pool = pool;
+ ctx->parent = parent;
+ ctx->interp = interp;
+ ctx->script = script;
+ ctx->script_info = sinfo;
+
+ if (parent == NULL)
+ ctx->nesting_depth = 0;
+ else
+ ctx->nesting_depth = parent->nesting_depth + 1;
+ return ctx;
+}
+
+static inline struct ext_include_interpreter_context *
+ext_include_get_interpreter_context(const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ return (struct ext_include_interpreter_context *)
+ sieve_interpreter_extension_get_context(interp, this_ext);
+}
+
+static inline struct ext_include_interpreter_context *
+ext_include_interpreter_context_init_child(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp,
+ struct ext_include_interpreter_context *parent,
+ struct sieve_script *script,
+ const struct ext_include_script_info *sinfo)
+{
+ struct ext_include_interpreter_context *ctx =
+ ext_include_interpreter_context_create(interp, parent,
+ script, sinfo);
+
+ sieve_interpreter_extension_register(interp, this_ext,
+ &include_interpreter_extension,
+ ctx);
+ return ctx;
+}
+
+void ext_include_interpreter_context_init(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_include_interpreter_context *ctx =
+ ext_include_get_interpreter_context(this_ext, interp);
+
+ /* Is this is the top-level interpreter ? */
+ if (ctx == NULL) {
+ struct sieve_script *script;
+
+ /* Initialize top context */
+ script = sieve_interpreter_script(interp);
+ ctx = ext_include_interpreter_context_create(interp, NULL,
+ script, NULL);
+
+ sieve_interpreter_extension_register(
+ interp, this_ext, &include_interpreter_extension,
+ (void *)ctx);
+ }
+}
+
+struct sieve_variable_storage *
+ext_include_interpreter_get_global_variables(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_include_interpreter_context *ctx =
+ ext_include_get_interpreter_context(this_ext, interp);
+
+ return ctx->global->var_storage;
+}
+
+/*
+ * Including a script during code generation
+ */
+
+int ext_include_generate_include(
+ const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+ enum ext_include_script_location location, enum ext_include_flags flags,
+ struct sieve_script *script,
+ const struct ext_include_script_info **included_r)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct ext_include_context *ext_ctx =
+ (struct ext_include_context *)this_ext->context;
+ int result = 1;
+ struct sieve_ast *ast;
+ struct sieve_binary *sbin = cgenv->sbin;
+ struct sieve_generator *gentr = cgenv->gentr;
+ struct ext_include_binary_context *binctx;
+ struct sieve_generator *subgentr;
+ struct ext_include_generator_context *ctx =
+ ext_include_get_generator_context(this_ext, gentr);
+ struct ext_include_generator_context *pctx;
+ struct sieve_error_handler *ehandler =
+ sieve_generator_error_handler(gentr);
+ struct ext_include_script_info *included;
+
+ *included_r = NULL;
+
+ /* Just to be sure: do not include more scripts when errors have occured
+ already.
+ */
+ if (sieve_get_errors(ehandler) > 0)
+ return -1;
+
+ /* Limit nesting level */
+ if (ctx->nesting_depth >= ext_ctx->max_nesting_depth) {
+ sieve_command_generate_error(
+ gentr, cmd,
+ "cannot nest includes deeper than %d levels",
+ ext_ctx->max_nesting_depth);
+ return -1;
+ }
+
+ /* Check for circular include */
+ if ((flags & EXT_INCLUDE_FLAG_ONCE) == 0) {
+ pctx = ctx;
+ while (pctx != NULL) {
+ if (sieve_script_equals(pctx->script, script)) {
+ /* Just drop circular include when uploading
+ inactive script; not an error
+ */
+ if ((cgenv->flags & SIEVE_COMPILE_FLAG_UPLOADED) != 0 &&
+ (cgenv->flags & SIEVE_COMPILE_FLAG_ACTIVATED) == 0) {
+ sieve_command_generate_warning(
+ gentr, cmd,
+ "circular include (ignored during upload)");
+ return 0;
+ }
+
+ sieve_command_generate_error(gentr, cmd,
+ "circular include");
+ return -1;
+ }
+
+ pctx = pctx->parent;
+ }
+ }
+
+ /* Get binary context */
+ binctx = ext_include_binary_init(this_ext, sbin, cgenv->ast);
+
+ /* Is the script already compiled into the current binary? */
+ included = ext_include_binary_script_get_include_info(binctx, script);
+ if (included != NULL) {
+ /* Yes, only update flags */
+ if ((flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0)
+ included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_OPTIONAL);
+ if ((flags & EXT_INCLUDE_FLAG_ONCE) == 0)
+ included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_ONCE);
+ } else {
+ const char *script_name = sieve_script_name(script);
+ enum sieve_compile_flags cpflags = cgenv->flags;
+
+ /* No, include new script */
+
+ /* Check whether include limit is exceeded */
+ if (ext_include_binary_script_get_count(binctx) >=
+ ext_ctx->max_includes) {
+ sieve_command_generate_error(
+ gentr, cmd, "failed to include script '%s': "
+ "no more than %u includes allowed",
+ str_sanitize(script_name, 80),
+ ext_ctx->max_includes);
+ return -1;
+ }
+
+ /* Allocate a new block in the binary and mark the script as
+ included. */
+ if (!sieve_script_is_open(script)) {
+ /* Just making an empty entry to mark a missing script
+ */
+ i_assert((flags & EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD) != 0 ||
+ (flags & EXT_INCLUDE_FLAG_OPTIONAL) != 0);
+ included = ext_include_binary_script_include(
+ binctx, location, flags, script, NULL);
+ result = 0;
+
+ } else {
+ struct sieve_binary_block *inc_block =
+ sieve_binary_block_create(sbin);
+
+ /* Real include */
+ included = ext_include_binary_script_include(
+ binctx, location, flags, script, inc_block);
+
+ /* Parse */
+ if ((ast = sieve_parse(script, ehandler,
+ NULL)) == NULL) {
+ sieve_command_generate_error(
+ gentr, cmd,
+ "failed to parse included script '%s'",
+ str_sanitize(script_name, 80));
+ return -1;
+ }
+
+ /* Included scripts inherit global variable scope */
+ (void)ext_include_create_ast_context(
+ this_ext, ast, cmd->ast_node->ast);
+
+ if (location == EXT_INCLUDE_LOCATION_GLOBAL) {
+ cpflags &=
+ ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+ } else {
+ cpflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+ }
+
+ /* Validate */
+ if (!sieve_validate(ast, ehandler, cpflags, NULL)) {
+ sieve_command_generate_error(
+ gentr, cmd,
+ "failed to validate included script '%s'",
+ str_sanitize(script_name, 80));
+ sieve_ast_unref(&ast);
+ return -1;
+ }
+
+ /* Generate
+
+ FIXME: It might not be a good idea to recurse code
+ generation for included scripts.
+ */
+ subgentr = sieve_generator_create(ast, ehandler, cpflags);
+ ext_include_initialize_generator_context(
+ cmd->ext, subgentr, ctx, script);
+
+ if (sieve_generator_run(subgentr, &inc_block) == NULL) {
+ sieve_command_generate_error(
+ gentr, cmd,
+ "failed to generate code for included script '%s'",
+ str_sanitize(script_name, 80));
+ result = -1;
+ }
+
+ sieve_generator_free(&subgentr);
+
+ /* Cleanup */
+ sieve_ast_unref(&ast);
+ }
+ }
+
+ if (result > 0)
+ *included_r = included;
+ return result;
+}
+
+/*
+ * Executing an included script during interpretation
+ */
+
+static bool
+ext_include_runtime_check_circular(
+ struct ext_include_interpreter_context *ctx,
+ const struct ext_include_script_info *include)
+{
+ struct ext_include_interpreter_context *pctx;
+
+ pctx = ctx;
+ while (pctx != NULL) {
+
+ if (sieve_script_equals(include->script, pctx->script))
+ return TRUE;
+
+ pctx = pctx->parent;
+ }
+
+ return FALSE;
+}
+
+static bool
+ext_include_runtime_include_mark(struct ext_include_interpreter_context *ctx,
+ const struct ext_include_script_info *include,
+ bool once)
+{
+ struct sieve_script *const *includes;
+ unsigned int count, i;
+
+ includes = array_get(&ctx->global->included_scripts, &count);
+ for (i = 0; i < count; i++) {
+ if (sieve_script_equals(include->script, includes[i]))
+ return (!once);
+ }
+
+ array_append(&ctx->global->included_scripts, &include->script, 1);
+ return TRUE;
+}
+
+int ext_include_execute_include(const struct sieve_runtime_env *renv,
+ unsigned int include_id,
+ enum ext_include_flags flags)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ int result = SIEVE_EXEC_OK;
+ struct ext_include_interpreter_context *ctx;
+ const struct ext_include_script_info *included;
+ struct ext_include_binary_context *binctx =
+ ext_include_binary_get_context(this_ext, renv->sbin);
+ bool once = ((flags & EXT_INCLUDE_FLAG_ONCE) != 0);
+ unsigned int block_id;
+
+ /* Check for invalid include id (== corrupt binary) */
+ included = ext_include_binary_script_get_included(binctx, include_id);
+ if (included == NULL) {
+ sieve_runtime_trace_error(
+ renv, "include: include id %d is invalid", include_id);
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ ctx = ext_include_get_interpreter_context(this_ext, renv->interp);
+ block_id = sieve_binary_block_get_id(included->block);
+
+ /* If :once modifier is specified, check for duplicate include */
+ if (ext_include_runtime_include_mark(ctx, included, once)) {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_NONE,
+ "include: start script '%s' [inc id: %d, block: %d]",
+ sieve_script_name(included->script),
+ include_id, block_id);
+ } else {
+ /* skip */
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_NONE,
+ "include: skipped include for script '%s' "
+ "[inc id: %d, block: %d]; already run once",
+ sieve_script_name(included->script),
+ include_id, block_id);
+ return result;
+ }
+
+ /* Check circular include during interpretation as well.
+ * Let's not trust binaries.
+ */
+ if (ext_include_runtime_check_circular(ctx, included)) {
+ sieve_runtime_trace_error(renv,
+ "include: circular include of script '%s' "
+ "[inc id: %d, block: %d]",
+ sieve_script_name(included->script),
+ include_id, block_id);
+
+ /* Situation has no valid way to emerge at runtime */
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ctx->parent == NULL) {
+ struct ext_include_interpreter_context *curctx = NULL;
+ struct sieve_error_handler *ehandler = renv->ehandler;
+ struct sieve_interpreter *subinterp;
+ bool interrupted = FALSE;
+
+ /* We are the top-level interpreter instance */
+ if (result == SIEVE_EXEC_OK) {
+ struct sieve_execute_env eenv_new = *eenv;
+
+ if (included->location != EXT_INCLUDE_LOCATION_GLOBAL)
+ eenv_new.flags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+ else {
+ eenv_new.flags &=
+ ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+ }
+
+ /* Create interpreter for top-level included script
+ (first sub-interpreter)
+ */
+ subinterp = sieve_interpreter_create_for_block(
+ included->block, included->script, renv->interp,
+ &eenv_new, ehandler);
+ if (subinterp != NULL) {
+ curctx = ext_include_interpreter_context_init_child(
+ this_ext, subinterp, ctx, included->script,
+ included);
+
+ /* Activate and start the top-level included script */
+ result = sieve_interpreter_start(
+ subinterp, renv->result, &interrupted);
+ } else {
+ result = SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Included scripts can have includes of their own. This is not
+ implemented recursively. Rather, the sub-interpreter
+ interrupts and defers the include to the top-level
+ interpreter, which is here. */
+ if (result == SIEVE_EXEC_OK && interrupted &&
+ !curctx->returned) {
+ while (result == SIEVE_EXEC_OK) {
+ if (((interrupted && curctx->returned) ||
+ (!interrupted)) &&
+ curctx->parent != NULL) {
+ const struct ext_include_script_info *ended_script =
+ curctx->script_info;
+
+ /* Sub-interpreter ended or executed
+ return */
+
+ /* Ascend interpreter stack */
+ curctx = curctx->parent;
+ sieve_interpreter_free(&subinterp);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+ "include: script '%s' ended "
+ "[inc id: %d, block: %d]",
+ sieve_script_name(ended_script->script),
+ ended_script->id,
+ sieve_binary_block_get_id(ended_script->block));
+
+ /* This is the top-most sub-interpreter,
+ bail out */
+ if (curctx->parent == NULL)
+ break;
+
+ subinterp = curctx->interp;
+
+ /* Continue parent */
+ curctx->include = NULL;
+ curctx->returned = FALSE;
+
+ result = sieve_interpreter_continue(
+ subinterp, &interrupted);
+ } else {
+ if (curctx->include != NULL) {
+ /* Sub-include requested */
+
+ if (result == SIEVE_EXEC_OK) {
+ struct sieve_execute_env eenv_new = *eenv;
+
+ if (curctx->include->location != EXT_INCLUDE_LOCATION_GLOBAL)
+ eenv_new.flags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+ else {
+ eenv_new.flags &=
+ ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+ }
+
+ /* Create sub-interpreter */
+ subinterp = sieve_interpreter_create_for_block(
+ curctx->include->block, curctx->include->script,
+ curctx->interp, &eenv_new, ehandler);
+ if (subinterp != NULL) {
+ curctx = ext_include_interpreter_context_init_child(
+ this_ext, subinterp, curctx,
+ curctx->include->script, curctx->include);
+
+ /* Start the sub-include's interpreter */
+ curctx->include = NULL;
+ curctx->returned = FALSE;
+ result = sieve_interpreter_start(
+ subinterp, renv->result, &interrupted);
+ } else {
+ result = SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+ } else {
+ /* Sub-interpreter was interrupted outside
+ this extension, probably stop command was
+ executed. Generate an interrupt ourselves,
+ ending all script execution. */
+ sieve_interpreter_interrupt(renv->interp);
+ break;
+ }
+ }
+ }
+ }
+
+ /* Free any sub-interpreters that might still be active */
+ while (curctx != NULL && curctx->parent != NULL) {
+ struct ext_include_interpreter_context *nextctx =
+ curctx->parent;
+ struct sieve_interpreter *killed_interp = curctx->interp;
+ const struct ext_include_script_info *ended_script =
+ curctx->script_info;
+
+ /* This kills curctx too */
+ sieve_interpreter_free(&killed_interp);
+
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_NONE,
+ "include: script '%s' ended [id: %d, block: %d]",
+ sieve_script_name(ended_script->script),
+ ended_script->id,
+ sieve_binary_block_get_id(ended_script->block));
+
+ /* Luckily we recorded the parent earlier */
+ curctx = nextctx;
+ }
+
+ } else {
+ /* We are an included script already, defer inclusion to main
+ interpreter */
+ ctx->include = included;
+ sieve_interpreter_interrupt(renv->interp);
+ }
+
+ return result;
+}
+
+void ext_include_execute_return(const struct sieve_runtime_env *renv)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_include_interpreter_context *ctx =
+ ext_include_get_interpreter_context(this_ext, renv->interp);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "return: exiting included script");
+ ctx->returned = TRUE;
+ sieve_interpreter_interrupt(renv->interp);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.h b/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.h
new file mode 100644
index 0000000..44bfe9a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.h
@@ -0,0 +1,170 @@
+#ifndef EXT_INCLUDE_COMMON_H
+#define EXT_INCLUDE_COMMON_H
+
+#include "lib.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+/*
+ * Forward declarations
+ */
+
+struct ext_include_script_info;
+struct ext_include_binary_context;
+
+/*
+ * Types
+ */
+
+enum ext_include_flags { // stored in one byte
+ EXT_INCLUDE_FLAG_ONCE = 0x01,
+ EXT_INCLUDE_FLAG_OPTIONAL = 0x02,
+ EXT_INCLUDE_FLAG_MISSING_AT_UPLOAD = 0x04
+};
+
+enum ext_include_script_location {
+ EXT_INCLUDE_LOCATION_PERSONAL,
+ EXT_INCLUDE_LOCATION_GLOBAL,
+ EXT_INCLUDE_LOCATION_INVALID
+};
+
+static inline const char *
+ext_include_script_location_name(enum ext_include_script_location location)
+{
+ switch (location) {
+ case EXT_INCLUDE_LOCATION_PERSONAL:
+ return "personal";
+ case EXT_INCLUDE_LOCATION_GLOBAL:
+ return "global";
+ default:
+ break;
+ }
+
+ return "[INVALID LOCATION]";
+}
+
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def include_extension;
+extern const struct sieve_binary_extension include_binary_ext;
+
+bool ext_include_load(const struct sieve_extension *ext, void **context);
+void ext_include_unload(const struct sieve_extension *ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_include;
+extern const struct sieve_command_def cmd_return;
+extern const struct sieve_command_def cmd_global;
+
+/* DEPRICATED */
+extern const struct sieve_command_def cmd_import;
+extern const struct sieve_command_def cmd_export;
+
+/*
+ * Operations
+ */
+
+enum ext_include_opcode {
+ EXT_INCLUDE_OPERATION_INCLUDE,
+ EXT_INCLUDE_OPERATION_RETURN,
+ EXT_INCLUDE_OPERATION_GLOBAL
+};
+
+extern const struct sieve_operation_def include_operation;
+extern const struct sieve_operation_def return_operation;
+extern const struct sieve_operation_def global_operation;
+
+/*
+ * Script access
+ */
+
+struct sieve_storage *
+ext_include_get_script_storage(const struct sieve_extension *ext,
+ enum ext_include_script_location location,
+ const char *script_name,
+ enum sieve_error *error_r);
+/*
+ * Context
+ */
+
+/* Extension context */
+
+struct ext_include_context {
+ /* Extension dependencies */
+ const struct sieve_extension *var_ext;
+
+ /* Configuration */
+ char *global_location;
+
+ struct sieve_storage *global_storage;
+ struct sieve_storage *personal_storage;
+
+ unsigned int max_nesting_depth;
+ unsigned int max_includes;
+};
+
+static inline struct ext_include_context *
+ext_include_get_context(const struct sieve_extension *ext)
+{
+ return (struct ext_include_context *) ext->context;
+}
+
+/* AST Context */
+
+struct ext_include_ast_context {
+ struct sieve_variable_scope *global_vars;
+
+ ARRAY(struct sieve_script *) included_scripts;
+};
+
+struct ext_include_ast_context *
+ext_include_create_ast_context(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast, struct sieve_ast *parent);
+struct ext_include_ast_context *
+ext_include_get_ast_context(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast);
+
+void ext_include_ast_link_included_script(
+ const struct sieve_extension *this_ext, struct sieve_ast *ast,
+ struct sieve_script *script);
+
+bool ext_include_validator_have_variables(
+ const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+/* Generator context */
+
+void ext_include_register_generator_context(
+ const struct sieve_extension *this_ext,
+ const struct sieve_codegen_env *cgenv);
+
+int ext_include_generate_include(
+ const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+ enum ext_include_script_location location, enum ext_include_flags flags,
+ struct sieve_script *script,
+ const struct ext_include_script_info **included_r);
+
+/* Interpreter context */
+
+void ext_include_interpreter_context_init(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp);
+
+int ext_include_execute_include(const struct sieve_runtime_env *renv,
+ unsigned int block_id,
+ enum ext_include_flags flags);
+void ext_include_execute_return(const struct sieve_runtime_env *renv);
+
+struct sieve_variable_storage *
+ext_include_interpreter_get_global_variables(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-limits.h b/pigeonhole/src/lib-sieve/plugins/include/ext-include-limits.h
new file mode 100644
index 0000000..37246c0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-limits.h
@@ -0,0 +1,9 @@
+#ifndef EXT_INCLUDE_LIMITS_H
+#define EXT_INCLUDE_LIMITS_H
+
+#include "sieve-common.h"
+
+#define EXT_INCLUDE_DEFAULT_MAX_NESTING_DEPTH 10
+#define EXT_INCLUDE_DEFAULT_MAX_INCLUDES 255
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.c b/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.c
new file mode 100644
index 0000000..28cd803
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.c
@@ -0,0 +1,254 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Variable import-export
+ */
+
+struct sieve_variable *ext_include_variable_import_global
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *variable)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_ast *ast = cmd->ast_node->ast;
+ struct ext_include_ast_context *ctx =
+ ext_include_get_ast_context(this_ext, ast);
+ struct ext_include_context *ectx = ext_include_get_context(this_ext);
+ struct sieve_variable_scope *local_scope;
+ struct sieve_variable_scope *global_scope = ctx->global_vars;
+ struct sieve_variable *global_var = NULL, *local_var;
+
+ /* Sanity safeguard */
+ i_assert ( ctx->global_vars != NULL );
+
+ if ( !sieve_variable_identifier_is_valid(variable) ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "invalid variable identifier '%s'", str_sanitize(variable,80));
+ return NULL;
+ }
+
+ /* Get/Declare the variable in the global scope */
+ global_var = sieve_variable_scope_declare(global_scope, variable);
+
+ /* Check whether scope is over its size limit */
+ if ( global_var == NULL ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "declaration of new global variable '%s' exceeds the limit "
+ "(max variables: %u)", variable,
+ sieve_variables_get_max_scope_size(ectx->var_ext));
+ return NULL;
+ }
+
+ /* Import the global variable into the local script scope */
+ local_scope = sieve_ext_variables_get_local_scope(ectx->var_ext, valdtr);
+
+ local_var = sieve_variable_scope_get_variable(local_scope, variable);
+ if ( local_var != NULL && local_var->ext != this_ext ) {
+ /* FIXME: indicate location of conflicting set statement */
+ sieve_command_validate_error(valdtr, cmd,
+ "declaration of new global variable '%s' conflicts with earlier local "
+ "use", variable);
+ return NULL;
+ }
+
+ return sieve_variable_scope_import(local_scope, global_var);
+}
+
+/*
+ * Binary symbol table
+ */
+
+bool ext_include_variables_save
+(struct sieve_binary_block *sblock,
+ struct sieve_variable_scope_binary *global_vars,
+ enum sieve_error *error_r ATTR_UNUSED)
+{
+ struct sieve_variable_scope *global_scope =
+ sieve_variable_scope_binary_get(global_vars);
+ unsigned int count = sieve_variable_scope_size(global_scope);
+ sieve_size_t jump;
+
+ sieve_binary_emit_unsigned(sblock, count);
+
+ jump = sieve_binary_emit_offset(sblock, 0);
+
+ if ( count > 0 ) {
+ unsigned int size, i;
+ struct sieve_variable *const *vars =
+ sieve_variable_scope_get_variables(global_scope, &size);
+
+ for ( i = 0; i < size; i++ ) {
+ sieve_binary_emit_cstring(sblock, vars[i]->identifier);
+ }
+ }
+
+ sieve_binary_resolve_offset(sblock, jump);
+
+ return TRUE;
+}
+
+bool ext_include_variables_load
+(const struct sieve_extension *this_ext, struct sieve_binary_block *sblock,
+ sieve_size_t *offset, struct sieve_variable_scope_binary **global_vars_r)
+{
+ struct ext_include_context *ectx =
+ ext_include_get_context(this_ext);
+
+ /* Sanity assert */
+ i_assert( *global_vars_r == NULL );
+
+ *global_vars_r = sieve_variable_scope_binary_read
+ (this_ext->svinst, ectx->var_ext, this_ext, sblock, offset);
+
+ return ( *global_vars_r != NULL );
+}
+
+bool ext_include_variables_dump
+(struct sieve_dumptime_env *denv,
+ struct sieve_variable_scope_binary *global_vars)
+{
+ struct sieve_variable_scope *global_scope =
+ sieve_variable_scope_binary_get(global_vars);
+ unsigned int size;
+ struct sieve_variable *const *vars;
+
+ i_assert(global_scope != NULL);
+
+ vars = sieve_variable_scope_get_variables(global_scope, &size);
+
+ if ( size > 0 ) {
+ unsigned int i;
+
+ sieve_binary_dump_sectionf(denv, "Global variables");
+
+ for ( i = 0; i < size; i++ ) {
+ sieve_binary_dumpf(denv, "%3d: '%s' \n", i, vars[i]->identifier);
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Global variables namespace
+ */
+
+bool vnspc_global_variables_validate
+ (struct sieve_validator *valdtr, const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment);
+bool vnspc_global_variables_generate
+ (const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd, void *var_data);
+
+static const struct sieve_variables_namespace_def
+global_variables_namespace = {
+ SIEVE_OBJECT("global", NULL, 0),
+ .validate = vnspc_global_variables_validate,
+ .generate = vnspc_global_variables_generate
+};
+
+bool vnspc_global_variables_validate
+(struct sieve_validator *valdtr,
+ const struct sieve_variables_namespace *nspc, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment ATTR_UNUSED)
+{
+ const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+ struct sieve_ast *ast = arg->ast;
+ struct ext_include_context *ectx =
+ ext_include_get_context(this_ext);
+ struct ext_include_ast_context *ctx =
+ ext_include_get_ast_context(this_ext, ast);
+ struct sieve_variable *var = NULL;
+ const struct sieve_variable_name *name_element;
+ const char *variable;
+
+ /* Sanity safeguard */
+ i_assert ( ctx->global_vars != NULL );
+
+ /* Check variable name */
+
+ if ( array_count(var_name) != 2 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "invalid variable name within global namespace: "
+ "encountered sub-namespace");
+ return FALSE;
+ }
+
+ name_element = array_idx(var_name, 1);
+ if ( name_element->num_variable >= 0 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "invalid variable name within global namespace: "
+ "encountered numeric variable name");
+ return FALSE;
+ }
+
+ variable = str_c(name_element->identifier);
+
+ /* Get/Declare the variable in the global scope */
+
+ var = sieve_variable_scope_declare(ctx->global_vars, variable);
+
+ if ( var == NULL ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "(implicit) declaration of new global variable '%s' exceeds the limit "
+ "(max variables: %u)", variable,
+ sieve_variables_get_max_scope_size(ectx->var_ext));
+ return FALSE;
+ }
+
+ *var_data = (void *) var;
+
+ return TRUE;
+}
+
+bool vnspc_global_variables_generate
+(const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+ const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+ struct ext_include_context *ectx = ext_include_get_context(this_ext);
+ struct sieve_variable *var = (struct sieve_variable *) var_data;
+
+ sieve_variables_opr_variable_emit(cgenv->sblock, ectx->var_ext, var);
+
+ return TRUE;
+}
+
+void ext_include_variables_global_namespace_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+ struct ext_include_context *ectx = ext_include_get_context(this_ext);
+
+ sieve_variables_namespace_register
+ (ectx->var_ext, valdtr, this_ext, &global_variables_namespace);
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.h b/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.h
new file mode 100644
index 0000000..d5927b2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include-variables.h
@@ -0,0 +1,41 @@
+#ifndef EXT_INCLUDE_VARIABLES_H
+#define EXT_INCLUDE_VARIABLES_H
+
+#include "sieve-common.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+
+/*
+ * Variable import-export
+ */
+
+struct sieve_variable *ext_include_variable_import_global
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *variable);
+
+/*
+ * Binary symbol table
+ */
+
+bool ext_include_variables_save
+ (struct sieve_binary_block *sblock,
+ struct sieve_variable_scope_binary *global_vars,
+ enum sieve_error *error_r);
+bool ext_include_variables_load
+ (const struct sieve_extension *this_ext, struct sieve_binary_block *sblock,
+ sieve_size_t *offset, struct sieve_variable_scope_binary **global_vars_r);
+bool ext_include_variables_dump
+ (struct sieve_dumptime_env *denv,
+ struct sieve_variable_scope_binary *global_vars);
+
+/*
+ * Validation
+ */
+
+void ext_include_variables_global_namespace_init
+ (const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/include/ext-include.c b/pigeonhole/src/lib-sieve/plugins/include/ext-include.c
new file mode 100644
index 0000000..0a38687
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/include/ext-include.c
@@ -0,0 +1,121 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension include
+ * -----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 6609
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: Current include implementation does not allow for parts of the script
+ * to be located in external binaries; all included scripts are recompiled and
+ * the resulting byte code is imported into the main binary in separate blocks.
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-include-common.h"
+#include "ext-include-binary.h"
+#include "ext-include-variables.h"
+
+/*
+ * Operations
+ */
+
+static const struct sieve_operation_def *ext_include_operations[] = {
+ &include_operation,
+ &return_operation,
+ &global_operation
+};
+
+/*
+ * Extension
+ */
+
+/* Forward declaration */
+
+static bool ext_include_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_include_generator_load
+ (const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+static bool ext_include_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+static bool ext_include_binary_load
+ (const struct sieve_extension *ext, struct sieve_binary *binary);
+
+/* Extension objects */
+
+const struct sieve_extension_def include_extension = {
+ .name = "include",
+ .version = 1,
+
+ .load = ext_include_load,
+ .unload = ext_include_unload,
+ .validator_load = ext_include_validator_load,
+ .generator_load = ext_include_generator_load,
+ .interpreter_load = ext_include_interpreter_load,
+ .binary_load = ext_include_binary_load,
+ .binary_dump = ext_include_binary_dump,
+ .code_dump = ext_include_code_dump,
+
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_include_operations)
+};
+
+static bool ext_include_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_include);
+ sieve_validator_register_command(valdtr, ext, &cmd_return);
+ sieve_validator_register_command(valdtr, ext, &cmd_global);
+
+ /* DEPRICATED */
+ sieve_validator_register_command(valdtr, ext, &cmd_import);
+ sieve_validator_register_command(valdtr, ext, &cmd_export);
+
+ /* Initialize global variables namespace */
+ ext_include_variables_global_namespace_init(ext, valdtr);
+
+ return TRUE;
+}
+
+static bool ext_include_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+ ext_include_register_generator_context(ext, cgenv);
+
+ return TRUE;
+}
+
+static bool ext_include_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ ext_include_interpreter_context_init(ext, renv->interp);
+
+ return TRUE;
+}
+
+static bool ext_include_binary_load
+(const struct sieve_extension *ext, struct sieve_binary *sbin)
+{
+ (void)ext_include_binary_get_context(ext, sbin);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/index/Makefile.am b/pigeonhole/src/lib-sieve/plugins/index/Makefile.am
new file mode 100644
index 0000000..434ca1c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_ext_index.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_index_la_SOURCES = \
+ ext-index-common.c \
+ ext-index.c \
+ tag-index.c
+
+noinst_HEADERS = \
+ ext-index-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/index/Makefile.in b/pigeonhole/src/lib-sieve/plugins/index/Makefile.in
new file mode 100644
index 0000000..852316c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/Makefile.in
@@ -0,0 +1,688 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/index
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_index_la_LIBADD =
+am_libsieve_ext_index_la_OBJECTS = ext-index-common.lo ext-index.lo \
+ tag-index.lo
+libsieve_ext_index_la_OBJECTS = $(am_libsieve_ext_index_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-index-common.Plo \
+ ./$(DEPDIR)/ext-index.Plo ./$(DEPDIR)/tag-index.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_index_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_index_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_index.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_index_la_SOURCES = \
+ ext-index-common.c \
+ ext-index.c \
+ tag-index.c
+
+noinst_HEADERS = \
+ ext-index-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/index/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/index/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_index.la: $(libsieve_ext_index_la_OBJECTS) $(libsieve_ext_index_la_DEPENDENCIES) $(EXTRA_libsieve_ext_index_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_index_la_OBJECTS) $(libsieve_ext_index_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-index-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-index.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag-index.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-index-common.Plo
+ -rm -f ./$(DEPDIR)/ext-index.Plo
+ -rm -f ./$(DEPDIR)/tag-index.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-index-common.Plo
+ -rm -f ./$(DEPDIR)/ext-index.Plo
+ -rm -f ./$(DEPDIR)/tag-index.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.c b/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.c
new file mode 100644
index 0000000..26fb706
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.c
@@ -0,0 +1,15 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "utc-offset.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+
+#include "ext-index-common.h"
+
diff --git a/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.h b/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.h
new file mode 100644
index 0000000..7ce3244
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/ext-index-common.h
@@ -0,0 +1,29 @@
+#ifndef EXT_INDEX_COMMON_H
+#define EXT_INDEX_COMMON_H
+
+#include "sieve-common.h"
+
+#include <time.h>
+
+#define SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE 100
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def index_tag;
+extern const struct sieve_argument_def last_tag;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def index_operand;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def index_extension;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/index/ext-index.c b/pigeonhole/src/lib-sieve/plugins/index/ext-index.c
new file mode 100644
index 0000000..33e7771
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/ext-index.c
@@ -0,0 +1,69 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension index
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5260
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-index-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_index_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def index_extension = {
+ .name = "index",
+ .validator_load = ext_index_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(index_operand)
+};
+
+static bool ext_index_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register :index and :last tags with header, address and date test commands
+ * and we don't care whether these command are registered or even whether
+ * these will be registered at all. The validator handles either situation
+ * gracefully.
+ */
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &last_tag, 0);
+
+ sieve_validator_register_external_tag
+ (valdtr, "address", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "address", ext, &last_tag, 0);
+
+ sieve_validator_register_external_tag
+ (valdtr, "date", ext, &index_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "date", ext, &last_tag, 0);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/index/tag-index.c b/pigeonhole/src/lib-sieve/plugins/index/tag-index.c
new file mode 100644
index 0000000..d8938a5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/index/tag-index.c
@@ -0,0 +1,278 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-index-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool
+tag_index_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd);
+static bool
+tag_index_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def index_tag = {
+ .identifier = "index",
+ .validate = tag_index_validate,
+ .generate = tag_index_generate
+};
+
+static bool
+tag_last_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd);
+
+const struct sieve_argument_def last_tag = {
+ .identifier = "last",
+ .validate = tag_last_validate,
+};
+
+/*
+ * Header override
+ */
+
+static bool
+svmo_index_dump_context(const struct sieve_message_override *svmo,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+svmo_index_read_context(const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **ho_context);
+static int
+svmo_index_header_override(const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv,
+ bool mime_decode, struct sieve_stringlist **headers);
+
+const struct sieve_message_override_def index_header_override = {
+ SIEVE_OBJECT("index", &index_operand, 0),
+ .sequence = SIEVE_EXT_INDEX_HDR_OVERRIDE_SEQUENCE,
+ .dump_context = svmo_index_dump_context,
+ .read_context = svmo_index_read_context,
+ .header_override = svmo_index_header_override
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_header_overrides =
+ SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(index_header_override);
+
+const struct sieve_operand_def index_operand = {
+ .name = "index operand",
+ .ext_def = &index_extension,
+ .class = &sieve_message_override_operand_class,
+ .interface = &ext_header_overrides
+};
+
+/*
+ * Tag data
+ */
+
+struct tag_index_data {
+ sieve_number_t fieldno;
+ bool last:1;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_index_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct tag_index_data *data;
+
+ /* Skip the tag itself */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* Check syntax:
+ * ":index" <fieldno: number>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_NUMBER, FALSE))
+ return FALSE;
+
+ if (tag->argument->data == NULL) {
+ data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1);
+ tag->argument->data = (void *)data;
+ } else {
+ data = (struct tag_index_data *)tag->argument->data;
+ }
+
+ data->fieldno = sieve_ast_argument_number(*arg);
+ if (data->fieldno == 0) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "the :index tag for the %s %s cannot be zero",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ /* Detach parameter */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+ return TRUE;
+}
+
+static bool
+tag_last_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *index_arg;
+ struct tag_index_data *data;
+
+ index_arg = sieve_command_find_argument(cmd, &index_tag);
+ if (index_arg == NULL) {
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "the :last tag for the %s %s cannot be specified "
+ "without the :index tag",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ /* Set :last flag */
+ if (index_arg->argument->data == NULL) {
+ data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1);
+ index_arg->argument->data = (void*)data;
+ } else {
+ data = (struct tag_index_data *)index_arg->argument->data;
+ }
+ data->last = TRUE;
+
+ /* Detach */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_index_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ struct tag_index_data *data =
+ (struct tag_index_data *)arg->argument->data;
+
+ if (sieve_ast_argument_type(arg) != SAAT_TAG)
+ return FALSE;
+
+ sieve_opr_message_override_emit(cgenv->sblock, arg->argument->ext,
+ &index_header_override);
+
+ (void)sieve_binary_emit_integer (cgenv->sblock, data->fieldno);
+ (void)sieve_binary_emit_byte(cgenv->sblock, (data->last ? 1 : 0));
+ return TRUE;
+}
+
+/*
+ * Header override implementation
+ */
+
+/* Context data */
+
+struct svmo_index_context {
+ unsigned int fieldno;
+ bool last:1;
+};
+
+/* Context coding */
+
+static bool
+svmo_index_dump_context(const struct sieve_message_override *svmo ATTR_UNUSED,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_number_t fieldno = 0;
+ unsigned int last;
+
+ if (!sieve_binary_read_integer(denv->sblock, address, &fieldno) ||
+ fieldno == 0)
+ return FALSE;
+
+ sieve_code_dumpf(denv, "fieldno: %llu", (unsigned long long) fieldno);
+
+ if (!sieve_binary_read_byte(denv->sblock, address, &last))
+ return FALSE;
+
+ if (last > 0)
+ sieve_code_dumpf(denv, "last");
+ return TRUE;
+}
+
+static int
+svmo_index_read_context(const struct sieve_message_override *svmo ATTR_UNUSED,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **ho_context)
+{
+ pool_t pool = sieve_result_pool(renv->result);
+ struct svmo_index_context *ctx;
+ sieve_number_t fieldno;
+ unsigned int last = 0;
+
+ if (!sieve_binary_read_integer(renv->sblock, address, &fieldno)) {
+ sieve_runtime_trace_error(renv, "fieldno: invalid number");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ if (fieldno == 0) {
+ sieve_runtime_trace_error(renv, "fieldno: index is zero");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ if (!sieve_binary_read_byte(renv->sblock, address, &last)) {
+ sieve_runtime_trace_error(renv, "last: invalid byte");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ ctx = p_new(pool, struct svmo_index_context, 1);
+ ctx->fieldno = fieldno;
+ ctx->last = (last == 0 ? FALSE : TRUE);
+
+ *ho_context = (void *) ctx;
+ return SIEVE_EXEC_OK;
+}
+
+/* Override */
+
+static int
+svmo_index_header_override(const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv,
+ bool mime_decode ATTR_UNUSED,
+ struct sieve_stringlist **headers)
+{
+ struct svmo_index_context *ctx =
+ (struct svmo_index_context *)svmo->context;
+
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_MATCHING,
+ "header index override: only returning index %d%s",
+ ctx->fieldno, (ctx->last ? " (from last)" : ""));
+
+ *headers = sieve_index_stringlist_create(
+ renv, *headers, (int)ctx->fieldno * (ctx->last ? -1 : 1));
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.am b/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.am
new file mode 100644
index 0000000..b627735
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES = libsieve_ext_mailbox.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tags = \
+ tag-mailbox-create.c
+
+tests = \
+ tst-mailboxexists.c
+
+libsieve_ext_mailbox_la_SOURCES = \
+ $(tags) \
+ $(tests) \
+ ext-mailbox.c
+
+public_headers = \
+ sieve-ext-mailbox.h
+
+headers = \
+ ext-mailbox-common.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in b/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in
new file mode 100644
index 0000000..6d5ed09
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in
@@ -0,0 +1,757 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/mailbox
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_mailbox_la_LIBADD =
+am__objects_1 = tag-mailbox-create.lo
+am__objects_2 = tst-mailboxexists.lo
+am_libsieve_ext_mailbox_la_OBJECTS = $(am__objects_1) $(am__objects_2) \
+ ext-mailbox.lo
+libsieve_ext_mailbox_la_OBJECTS = \
+ $(am_libsieve_ext_mailbox_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-mailbox.Plo \
+ ./$(DEPDIR)/tag-mailbox-create.Plo \
+ ./$(DEPDIR)/tst-mailboxexists.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_mailbox_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_mailbox_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_mailbox.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tags = \
+ tag-mailbox-create.c
+
+tests = \
+ tst-mailboxexists.c
+
+libsieve_ext_mailbox_la_SOURCES = \
+ $(tags) \
+ $(tests) \
+ ext-mailbox.c
+
+public_headers = \
+ sieve-ext-mailbox.h
+
+headers = \
+ ext-mailbox-common.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/mailbox/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/mailbox/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_mailbox.la: $(libsieve_ext_mailbox_la_OBJECTS) $(libsieve_ext_mailbox_la_DEPENDENCIES) $(EXTRA_libsieve_ext_mailbox_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_mailbox_la_OBJECTS) $(libsieve_ext_mailbox_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-mailbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag-mailbox-create.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-mailboxexists.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-mailbox.Plo
+ -rm -f ./$(DEPDIR)/tag-mailbox-create.Plo
+ -rm -f ./$(DEPDIR)/tst-mailboxexists.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-mailbox.Plo
+ -rm -f ./$(DEPDIR)/tag-mailbox-create.Plo
+ -rm -f ./$(DEPDIR)/tst-mailboxexists.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox-common.h b/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox-common.h
new file mode 100644
index 0000000..96cff39
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox-common.h
@@ -0,0 +1,39 @@
+#ifndef EXT_MAILBOX_COMMON_H
+#define EXT_MAILBOX_COMMON_H
+
+#include "sieve-common.h"
+
+#include "sieve-ext-mailbox.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def mailbox_create_tag;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def mailboxexists_test;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def mailbox_create_operand;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def mailboxexists_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def mailbox_extension;
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox.c b/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox.c
new file mode 100644
index 0000000..a2dcd88
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/ext-mailbox.c
@@ -0,0 +1,72 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension mailbox
+ * ------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Tag registration
+ */
+
+void sieve_ext_mailbox_register_create_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *mailbox_ext,
+ const char *command)
+{
+ if ( sieve_validator_extension_loaded(valdtr, mailbox_ext) ) {
+ sieve_validator_register_external_tag(valdtr, command,
+ mailbox_ext, &mailbox_create_tag, SIEVE_OPT_SIDE_EFFECT);
+ }
+}
+
+
+/*
+ * Extension
+ */
+
+static bool ext_mailbox_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mailbox_extension = {
+ .name = "mailbox",
+ .validator_load = ext_mailbox_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(mailboxexists_operation),
+ SIEVE_EXT_DEFINE_OPERAND(mailbox_create_operand)
+};
+
+static bool ext_mailbox_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register :create tag with fileinto command and we don't care whether this
+ * command is registered or even whether it will be registered at all. The
+ * validator handles either situation gracefully
+ */
+ sieve_validator_register_external_tag
+ (valdtr, "fileinto", ext, &mailbox_create_tag, SIEVE_OPT_SIDE_EFFECT);
+
+ /* Register new test */
+ sieve_validator_register_command(valdtr, ext, &mailboxexists_test);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/sieve-ext-mailbox.h b/pigeonhole/src/lib-sieve/plugins/mailbox/sieve-ext-mailbox.h
new file mode 100644
index 0000000..f7fa4cf
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/sieve-ext-mailbox.h
@@ -0,0 +1,21 @@
+#ifndef SIEVE_EXT_MAILBOX_H
+#define SIEVE_EXT_MAILBOX_H
+
+/* sieve_ext_mailbox_get_extension():
+ * Get the extension struct for the mailbox extension.
+ */
+static inline const struct sieve_extension *sieve_ext_mailbox_get_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_get_by_name(svinst, "mailbox");
+}
+
+/* sieve_ext_mailbox_register_create_tag():
+ * Register the :create tagged argument for a command other than fileinto and
+ * redirect.
+ */
+void sieve_ext_mailbox_register_create_tag
+ (struct sieve_validator *valdtr, const struct sieve_extension *mailbox_ext,
+ const char *command);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c b/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
new file mode 100644
index 0000000..f3c64cf
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/tag-mailbox-create.c
@@ -0,0 +1,186 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+#include "sieve-generator.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool
+tag_mailbox_create_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+tag_mailbox_create_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def mailbox_create_tag = {
+ .identifier = "create",
+ .validate = tag_mailbox_create_validate,
+ .generate = tag_mailbox_create_generate
+};
+
+/*
+ * Side effect
+ */
+
+static void
+seff_mailbox_create_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep);
+static int
+seff_mailbox_create_pre_execute(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context,
+ void **se_tr_context ATTR_UNUSED);
+
+const struct sieve_side_effect_def mailbox_create_side_effect = {
+ SIEVE_OBJECT("create", &mailbox_create_operand, 0),
+ .precedence = 100,
+ .to_action = &act_store,
+ .print = seff_mailbox_create_print,
+ .pre_execute = seff_mailbox_create_pre_execute
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+ SIEVE_EXT_DEFINE_SIDE_EFFECT(mailbox_create_side_effect);
+
+const struct sieve_operand_def mailbox_create_operand = {
+ .name = "create operand",
+ .ext_def = &mailbox_extension,
+ .class = &sieve_side_effect_operand_class,
+ .interface = &ext_side_effects
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_mailbox_create_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_mailbox_create_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED)
+{
+ if (sieve_ast_argument_type(arg) != SAAT_TAG)
+ return FALSE;
+
+ sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
+ &mailbox_create_side_effect);
+ return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+static void
+seff_mailbox_create_print(const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ sieve_result_seffect_printf(
+ rpenv, "create mailbox if it does not exist");
+}
+
+static int
+seff_mailbox_create_pre_execute(
+ const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_action_exec_env *aenv, void *tr_context,
+ void **se_tr_context ATTR_UNUSED)
+{
+ struct act_store_transaction *trans = tr_context;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct mailbox *box = trans->box;
+
+ /* Check whether creation is necessary */
+ if (box == NULL || trans->disabled)
+ return SIEVE_EXEC_OK;
+
+ eenv->exec_status->last_storage = mailbox_get_storage(box);
+
+ /* Open the mailbox (may already be open) */
+ if (trans->error_code == MAIL_ERROR_NONE) {
+ if (mailbox_open(box) < 0)
+ sieve_act_store_get_storage_error(aenv, trans);
+ }
+
+ /* Check whether creation has a chance of working */
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ return SIEVE_EXEC_OK;
+ case MAIL_ERROR_NOTFOUND:
+ break;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ trans->error = NULL;
+ trans->error_code = MAIL_ERROR_NONE;
+
+ /* Create mailbox */
+ if (mailbox_create(box, NULL, FALSE) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+ if (trans->error_code == MAIL_ERROR_EXISTS) {
+ trans->error = NULL;
+ trans->error_code = MAIL_ERROR_NONE;
+ } else {
+ return (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ }
+ }
+
+ /* Subscribe to it if necessary */
+ if (eenv->scriptenv->mailbox_autosubscribe) {
+ (void)mailbox_list_set_subscribed(
+ mailbox_get_namespace(box)->list,
+ mailbox_get_name(box), TRUE);
+ }
+
+ /* Try opening again */
+ if (mailbox_open(box) < 0) {
+ /* Failed definitively */
+ sieve_act_store_get_storage_error(aenv, trans);
+ return (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ }
+ return SIEVE_EXEC_OK;
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c b/pigeonhole/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
new file mode 100644
index 0000000..e0fd33d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mailbox/tst-mailboxexists.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-actions.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-mailbox-common.h"
+
+/*
+ * Mailboxexists command
+ *
+ * Syntax:
+ * mailboxexists <mailbox-names: string-list>
+ */
+
+static bool
+tst_mailboxexists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_mailboxexists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def mailboxexists_test = {
+ .identifier = "mailboxexists",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_mailboxexists_validate,
+ .generate = tst_mailboxexists_generate,
+};
+
+/*
+ * Mailboxexists operation
+ */
+
+static bool
+tst_mailboxexists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_mailboxexists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def mailboxexists_operation = {
+ .mnemonic = "MAILBOXEXISTS",
+ .ext_def = &mailbox_extension,
+ .dump = tst_mailboxexists_operation_dump,
+ .execute = tst_mailboxexists_operation_execute,
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+ struct sieve_validator *valdtr;
+ struct sieve_command *tst;
+};
+
+static int
+tst_mailboxexists_mailbox_validate(void *context,
+ struct sieve_ast_argument *arg)
+{
+ struct _validate_context *valctx =
+ (struct _validate_context *)context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *mailbox = sieve_ast_argument_strc(arg), *error;
+
+ if (!sieve_mailbox_check_name(mailbox, &error)) {
+ sieve_argument_validate_warning(
+ valctx->valdtr, arg, "%s test: "
+ "invalid mailbox name `%s' specified: %s",
+ sieve_command_identifier(valctx->tst),
+ str_sanitize(mailbox, 256), error);
+ }
+ }
+
+ return 1;
+}
+
+static bool
+tst_mailboxexists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *aarg;
+ struct _validate_context valctx;
+
+ if (!sieve_validate_positional_argument(
+ valdtr, tst, arg, "mailbox-names", 1, SAAT_STRING_LIST))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ aarg = arg;
+ i_zero(&valctx);
+ valctx.valdtr = valdtr;
+ valctx.tst = tst;
+
+ return (sieve_ast_stringlist_map(
+ &aarg, (void*)&valctx,
+ tst_mailboxexists_mailbox_validate) >= 0);
+}
+
+/*
+ * Test generation
+ */
+
+static bool
+tst_mailboxexists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &mailboxexists_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_mailboxexists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "MAILBOXEXISTS");
+ sieve_code_descend(denv);
+
+ return sieve_opr_stringlist_dump(denv, address, "mailbox-names");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_mailboxexists_test_mailbox(const struct sieve_runtime_env *renv,
+ const char *mailbox, bool trace,
+ bool *all_exist_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct mailbox *box;
+ const char *error;
+
+ /* Check validity of mailbox name */
+ if (!sieve_mailbox_check_name(mailbox, &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "mailboxexists test: "
+ "invalid mailbox name `%s' specified: %s",
+ str_sanitize(mailbox, 256), error);
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Open the box */
+ box = mailbox_alloc_for_user(eenv->scriptenv->user,
+ mailbox,
+ MAILBOX_FLAG_POST_SESSION);
+
+ if (mailbox_open(box) < 0) {
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0,
+ "mailbox `%s' cannot be opened",
+ str_sanitize(mailbox, 80));
+ }
+ mailbox_free(&box);
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Also fail when it is readonly */
+ if (mailbox_is_readonly(box)) {
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0,
+ "mailbox `%s' is read-only",
+ str_sanitize(mailbox, 80));
+ }
+ mailbox_free(&box);
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* FIXME: check acl for 'p' or 'i' ACL permissions as
+ required by RFC */
+
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox `%s' exists",
+ str_sanitize(mailbox, 80));
+ }
+
+ /* Close mailbox */
+ mailbox_free(&box);
+ return SIEVE_EXEC_OK;
+}
+
+static int
+tst_mailboxexists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_stringlist *mailbox_names;
+ string_t *mailbox_item;
+ bool trace = FALSE;
+ bool all_exist = TRUE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read notify uris */
+ ret = sieve_opr_stringlist_read(renv, address, "mailbox-names",
+ &mailbox_names);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+ sieve_runtime_trace(renv, 0, "mailboxexists test");
+ sieve_runtime_trace_descend(renv);
+
+ trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ }
+
+ if (eenv->scriptenv->user == NULL) {
+ sieve_runtime_trace(renv, 0, "no mail user; yield true");
+ sieve_interpreter_set_test_result(renv->interp, TRUE);
+ return SIEVE_EXEC_OK;
+ }
+
+ mailbox_item = NULL;
+ while (all_exist &&
+ (ret = sieve_stringlist_next_item(mailbox_names,
+ &mailbox_item)) > 0) {
+ const char *mailbox = str_c(mailbox_item);
+
+ ret = tst_mailboxexists_test_mailbox(renv, mailbox,
+ trace, &all_exist);
+ if (ret <= 0)
+ return ret;
+ }
+
+ if (ret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid mailbox name item");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (trace) {
+ if (all_exist) {
+ sieve_runtime_trace(renv, 0,
+ "all mailboxes are available");
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "some mailboxes are unavailable");
+ }
+ }
+
+ sieve_interpreter_set_test_result(renv->interp, all_exist);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.am b/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.am
new file mode 100644
index 0000000..6d69ba8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.am
@@ -0,0 +1,24 @@
+noinst_LTLIBRARIES = libsieve_ext_metadata.la
+
+libsieve_ext_metadata_la_LDFLAGS = -module -avoid-version
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_STORAGE_INCLUDE)
+
+tests = \
+ tst-metadata.c \
+ tst-metadataexists.c
+
+extensions = \
+ ext-metadata.c
+
+libsieve_ext_metadata_la_SOURCES = \
+ $(tests) \
+ $(extensions)
+
+noinst_HEADERS = \
+ ext-metadata-common.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in b/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in
new file mode 100644
index 0000000..0b95819
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in
@@ -0,0 +1,705 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/metadata
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_metadata_la_LIBADD =
+am__objects_1 = tst-metadata.lo tst-metadataexists.lo
+am__objects_2 = ext-metadata.lo
+am_libsieve_ext_metadata_la_OBJECTS = $(am__objects_1) \
+ $(am__objects_2)
+libsieve_ext_metadata_la_OBJECTS = \
+ $(am_libsieve_ext_metadata_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libsieve_ext_metadata_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(libsieve_ext_metadata_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-metadata.Plo \
+ ./$(DEPDIR)/tst-metadata.Plo \
+ ./$(DEPDIR)/tst-metadataexists.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_metadata_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_metadata_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_metadata.la
+libsieve_ext_metadata_la_LDFLAGS = -module -avoid-version
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_STORAGE_INCLUDE)
+
+tests = \
+ tst-metadata.c \
+ tst-metadataexists.c
+
+extensions = \
+ ext-metadata.c
+
+libsieve_ext_metadata_la_SOURCES = \
+ $(tests) \
+ $(extensions)
+
+noinst_HEADERS = \
+ ext-metadata-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/metadata/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/metadata/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_metadata.la: $(libsieve_ext_metadata_la_OBJECTS) $(libsieve_ext_metadata_la_DEPENDENCIES) $(EXTRA_libsieve_ext_metadata_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libsieve_ext_metadata_la_LINK) $(libsieve_ext_metadata_la_OBJECTS) $(libsieve_ext_metadata_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-metadata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-metadata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-metadataexists.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-metadata.Plo
+ -rm -f ./$(DEPDIR)/tst-metadata.Plo
+ -rm -f ./$(DEPDIR)/tst-metadataexists.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-metadata.Plo
+ -rm -f ./$(DEPDIR)/tst-metadata.Plo
+ -rm -f ./$(DEPDIR)/tst-metadataexists.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata-common.h b/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata-common.h
new file mode 100644
index 0000000..a4b25a0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_METADATA_COMMON_H
+#define EXT_METADATA_COMMON_H
+
+#include "lib.h"
+#include "mail-storage.h"
+#include "imap-metadata.h"
+
+#include "sieve-common.h"
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def mboxmetadata_extension;
+extern const struct sieve_extension_def servermetadata_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def metadata_test;
+extern const struct sieve_command_def servermetadata_test;
+extern const struct sieve_command_def metadataexists_test;
+extern const struct sieve_command_def servermetadataexists_test;
+
+/*
+ * Operations
+ */
+
+enum ext_metadata_opcode {
+ EXT_METADATA_OPERATION_METADATA,
+ EXT_METADATA_OPERATION_METADATAEXISTS
+};
+
+extern const struct sieve_operation_def metadata_operation;
+extern const struct sieve_operation_def servermetadata_operation;
+extern const struct sieve_operation_def metadataexists_operation;
+extern const struct sieve_operation_def servermetadataexists_operation;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata.c b/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata.c
new file mode 100644
index 0000000..882116b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/ext-metadata.c
@@ -0,0 +1,83 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-metadata-common.h"
+
+/*
+ * Extension mboxmetadata
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490; Section 3
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+const struct sieve_operation_def *mboxmetadata_operations[] = {
+ &metadata_operation,
+ &metadataexists_operation,
+};
+
+static bool ext_mboxmetadata_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mboxmetadata_extension = {
+ .name = "mboxmetadata",
+ .validator_load = ext_mboxmetadata_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(mboxmetadata_operations)
+};
+
+static bool ext_mboxmetadata_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_validator_register_command(valdtr, ext, &metadata_test);
+ sieve_validator_register_command(valdtr, ext, &metadataexists_test);
+
+ return TRUE;
+}
+
+/*
+ * Extension servermetadata
+ * -----------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5490; Section 4
+ * Implementation: skeleton
+ * Status: development
+ *
+ */
+
+const struct sieve_operation_def *servermetadata_operations[] = {
+ &servermetadata_operation,
+ &servermetadataexists_operation,
+};
+
+static bool ext_servermetadata_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def servermetadata_extension = {
+ .name = "servermetadata",
+ .validator_load = ext_servermetadata_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(servermetadata_operations)
+};
+
+static bool ext_servermetadata_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_validator_register_command(valdtr, ext, &servermetadata_test);
+ sieve_validator_register_command(valdtr, ext, &servermetadataexists_test);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c b/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c
new file mode 100644
index 0000000..2e14535
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c
@@ -0,0 +1,433 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-metadata-common.h"
+
+#define TST_METADATA_MAX_MATCH_SIZE SIEVE_MAX_STRING_LEN
+
+/*
+ * Test definitions
+ */
+
+/* Forward declarations */
+
+static bool
+tst_metadata_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+tst_metadata_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_metadata_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+/* Metadata test
+ *
+ * Syntax:
+ * metadata [MATCH-TYPE] [COMPARATOR]
+ * <mailbox: string>
+ * <annotation-name: string> <key-list: string-list>
+ */
+
+const struct sieve_command_def metadata_test = {
+ .identifier = "metadata",
+ .type = SCT_TEST,
+ .positional_args = 3,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_metadata_registered,
+ .validate = tst_metadata_validate,
+ .generate = tst_metadata_generate,
+};
+
+/* Servermetadata test
+ *
+ * Syntax:
+ * servermetadata [MATCH-TYPE] [COMPARATOR]
+ * <annotation-name: string> <key-list: string-list>
+ */
+
+const struct sieve_command_def servermetadata_test = {
+ .identifier = "servermetadata",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_metadata_registered,
+ .validate = tst_metadata_validate,
+ .generate = tst_metadata_generate,
+};
+
+/*
+ * Opcode definitions
+ */
+
+static bool
+tst_metadata_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_metadata_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+/* Metadata operation */
+
+const struct sieve_operation_def metadata_operation = {
+ .mnemonic = "METADATA",
+ .ext_def = &mboxmetadata_extension,
+ .code = EXT_METADATA_OPERATION_METADATA,
+ .dump = tst_metadata_operation_dump,
+ .execute = tst_metadata_operation_execute,
+};
+
+/* Servermetadata operation */
+
+const struct sieve_operation_def servermetadata_operation = {
+ .mnemonic = "SERVERMETADATA",
+ .ext_def = &servermetadata_extension,
+ .code = EXT_METADATA_OPERATION_METADATA,
+ .dump = tst_metadata_operation_dump,
+ .execute = tst_metadata_operation_execute,
+};
+
+/*
+ * Test registration
+ */
+
+static bool
+tst_metadata_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg,
+ SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg,
+ SIEVE_MATCH_OPT_MATCH_TYPE);
+ return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool
+tst_metadata_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ unsigned int arg_index = 1;
+ const char *error;
+
+ /* mailbox */
+ if (sieve_command_is(tst, metadata_test)) {
+ if (!sieve_validate_positional_argument(
+ valdtr, tst, arg, "mailbox", arg_index++,
+ SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Check name validity when mailbox argument is not a variable
+ */
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *mailbox = sieve_ast_argument_strc(arg);
+ const char *error;
+
+ if (!sieve_mailbox_check_name(mailbox, &error)) {
+ sieve_argument_validate_warning(
+ valdtr, arg, "%s test: "
+ "invalid mailbox name `%s' specified: %s",
+ sieve_command_identifier(tst),
+ str_sanitize(mailbox, 256), error);
+ }
+ }
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* annotation-name */
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "annotation-name", arg_index++,
+ SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *aname = sieve_ast_argument_str(arg);
+
+ if (!imap_metadata_verify_entry_name(str_c(aname), &error)) {
+ sieve_argument_validate_warning(
+ valdtr, arg, "%s test: "
+ "specified annotation name `%s' is invalid: %s",
+ sieve_command_identifier(tst),
+ str_sanitize(str_c(aname), 256),
+ sieve_error_from_external(error));
+ }
+ }
+
+ arg = sieve_ast_argument_next(arg);
+
+ /* key-list */
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "key-list", arg_index++,
+ SAAT_STRING_LIST))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate(valdtr, tst, arg,
+ &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool
+tst_metadata_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ if (sieve_command_is(tst, metadata_test)) {
+ sieve_operation_emit(cgenv->sblock, tst->ext,
+ &metadata_operation);
+ } else if (sieve_command_is(tst, servermetadata_test)) {
+ sieve_operation_emit(cgenv->sblock, tst->ext,
+ &servermetadata_operation);
+ } else {
+ i_unreached();
+ }
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, tst, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_metadata_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ bool metadata = sieve_operation_is(denv->oprtn, metadata_operation);
+
+ if (metadata)
+ sieve_code_dumpf(denv, "METADATA");
+ else
+ sieve_code_dumpf(denv, "SERVERMETADATA");
+
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ if (sieve_match_opr_optional_dump(denv, address, NULL) != 0)
+ return FALSE;
+ if (metadata && !sieve_opr_string_dump(denv, address, "mailbox"))
+ return FALSE;
+
+ return (sieve_opr_string_dump(denv, address, "annotation-name") &&
+ sieve_opr_stringlist_dump(denv, address, "key list"));
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_metadata_get_annotation(const struct sieve_runtime_env *renv,
+ const char *mailbox, const char *aname,
+ const char **annotation_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct mail_user *user = eenv->scriptenv->user;
+ struct mailbox *box;
+ struct imap_metadata_transaction *imtrans;
+ struct mail_attribute_value avalue;
+ int status, ret;
+
+ *annotation_r = NULL;
+
+ if (user == NULL)
+ return SIEVE_EXEC_OK;
+
+ if (mailbox != NULL) {
+ struct mail_namespace *ns;
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ box = mailbox_alloc(ns->list, mailbox, 0);
+ imtrans = imap_metadata_transaction_begin(box);
+ } else {
+ box = NULL;
+ imtrans = imap_metadata_transaction_begin_server(user);
+ }
+
+ status = SIEVE_EXEC_OK;
+ ret = imap_metadata_get(imtrans, aname, &avalue);
+ if (ret < 0) {
+ enum mail_error error_code;
+ const char *error;
+
+ error = imap_metadata_transaction_get_last_error(
+ imtrans, &error_code);
+
+ sieve_runtime_error(
+ renv, NULL, "%s test: "
+ "failed to retrieve annotation `%s': %s%s",
+ (mailbox != NULL ? "metadata" : "servermetadata"),
+ str_sanitize(aname, 256),
+ sieve_error_from_external(error),
+ (error_code == MAIL_ERROR_TEMP ?
+ " (temporary failure)" : ""));
+
+ status = (error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+
+ } else if (avalue.value != NULL) {
+ *annotation_r = avalue.value;
+ }
+ (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+ if (box != NULL)
+ mailbox_free(&box);
+ return status;
+}
+
+static int
+tst_metadata_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ bool metadata = sieve_operation_is(renv->oprtn, metadata_operation);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ string_t *mailbox, *aname;
+ struct sieve_stringlist *value_list, *key_list;
+ const char *annotation = NULL, *error;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Handle match-type and comparator operands */
+ if (sieve_match_opr_optional_read(renv, address, NULL,
+ &ret, &cmp, &mcht) < 0)
+ return ret;
+
+ /* Read mailbox */
+ if (metadata) {
+ ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* Read annotation-name */
+ ret = sieve_opr_string_read(renv, address, "annotation-name", &aname);
+ if (ret <= 0)
+ return ret;
+
+ /* Read key-list */
+ ret = sieve_opr_stringlist_read(renv, address, "key-list", &key_list);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (metadata) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "metadata test");
+ } else {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "servermetadata test");
+ }
+ sieve_runtime_trace_descend(renv);
+
+ if (!imap_metadata_verify_entry_name(str_c(aname), &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "%s test: "
+ "specified annotation name `%s' is invalid: %s",
+ (metadata ? "metadata" : "servermetadata"),
+ str_sanitize(str_c(aname), 256),
+ sieve_error_from_external(error));
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (metadata) {
+ if (!sieve_mailbox_check_name(str_c(mailbox), &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "metadata test: "
+ "invalid mailbox name `%s' specified: %s",
+ str_sanitize(str_c(mailbox), 256), error);
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "retrieving annotation `%s' from mailbox `%s'",
+ str_sanitize(str_c(aname), 256),
+ str_sanitize(str_c(mailbox), 80));
+ } else {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "retrieving server annotation `%s'",
+ str_sanitize(str_c(aname), 256));
+ }
+
+ /* Get annotation */
+ ret = tst_metadata_get_annotation(renv,
+ (metadata ? str_c(mailbox) : NULL),
+ str_c(aname), &annotation);
+ if (ret == SIEVE_EXEC_OK) {
+ /* Perform match */
+ if (annotation != NULL) {
+ /* Create value stringlist */
+ value_list = sieve_single_stringlist_create_cstr(
+ renv, annotation, FALSE);
+
+ /* Perform match */
+ match = sieve_match(renv, &mcht, &cmp,
+ value_list, key_list, &ret);
+ if (ret < 0)
+ return ret;
+ } else {
+ match = 0;
+ }
+ }
+
+ /* Set test result for subsequent conditional jump */
+ if (ret == SIEVE_EXEC_OK)
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return ret;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.c b/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.c
new file mode 100644
index 0000000..e11d692
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.c
@@ -0,0 +1,431 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-metadata-common.h"
+
+/*
+ * Command definitions
+ */
+
+/* Forward declarations */
+
+static bool
+tst_metadataexists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+/* Metadataexists command
+ *
+ * Syntax:
+ * metadataexists <mailbox: string> <annotation-names: string-list>
+ */
+
+static bool
+tst_metadataexists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def metadataexists_test = {
+ .identifier = "metadataexists",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_metadataexists_validate,
+ .generate = tst_metadataexists_generate,
+};
+
+/* Servermetadataexists command
+ *
+ * Syntax:
+ * servermetadataexists <annotation-names: string-list>
+ */
+
+const struct sieve_command_def servermetadataexists_test = {
+ .identifier = "servermetadataexists",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_metadataexists_validate,
+ .generate = tst_metadataexists_generate,
+};
+
+/*
+ * Opcode definitions
+ */
+
+static bool
+tst_metadataexists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_metadataexists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+/* Metadata operation */
+
+const struct sieve_operation_def metadataexists_operation = {
+ .mnemonic = "METADATAEXISTS",
+ .ext_def = &mboxmetadata_extension,
+ .code = EXT_METADATA_OPERATION_METADATAEXISTS,
+ .dump = tst_metadataexists_operation_dump,
+ .execute = tst_metadataexists_operation_execute,
+};
+
+/* Mailboxexists operation */
+
+const struct sieve_operation_def servermetadataexists_operation = {
+ .mnemonic = "SERVERMETADATAEXISTS",
+ .ext_def = &servermetadata_extension,
+ .code = EXT_METADATA_OPERATION_METADATAEXISTS,
+ .dump = tst_metadataexists_operation_dump,
+ .execute = tst_metadataexists_operation_execute,
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+ struct sieve_validator *valdtr;
+ struct sieve_command *tst;
+};
+
+static int
+tst_metadataexists_annotation_validate(void *context,
+ struct sieve_ast_argument *arg)
+{
+ struct _validate_context *valctx =
+ (struct _validate_context *)context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *aname = sieve_ast_strlist_strc(arg);
+ const char *error;
+
+ if (!imap_metadata_verify_entry_name(aname, &error)) {
+ sieve_argument_validate_warning(
+ valctx->valdtr, arg, "%s test: "
+ "specified annotation name `%s' is invalid: %s",
+ sieve_command_identifier(valctx->tst),
+ str_sanitize(aname, 256),
+ sieve_error_from_external(error));
+ }
+ }
+ return 1; /* Can't check at compile time */
+}
+
+static bool
+tst_metadataexists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *aarg;
+ struct _validate_context valctx;
+ unsigned int arg_index = 1;
+
+ if (sieve_command_is(tst, metadataexists_test)) {
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "mailbox", arg_index++,
+ SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Check name validity when mailbox argument is not a variable */
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *mailbox = sieve_ast_argument_strc(arg);
+ const char *error;
+
+ if (!sieve_mailbox_check_name(mailbox, &error)) {
+ sieve_argument_validate_warning(
+ valdtr, arg, "%s test: "
+ "invalid mailbox name `%s' specified: %s",
+ sieve_command_identifier(tst),
+ str_sanitize(mailbox, 256), error);
+ }
+ }
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ if (!sieve_validate_positional_argument(valdtr, tst, arg,
+ "annotation-names", arg_index++,
+ SAAT_STRING_LIST))
+ return FALSE;
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ aarg = arg;
+ i_zero(&valctx);
+ valctx.valdtr = valdtr;
+ valctx.tst = tst;
+
+ return (sieve_ast_stringlist_map(
+ &aarg, (void*)&valctx,
+ tst_metadataexists_annotation_validate) >= 0);
+}
+
+/*
+ * Test generation
+ */
+
+static bool
+tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ if (sieve_command_is(tst, metadataexists_test)) {
+ sieve_operation_emit(cgenv->sblock, tst->ext,
+ &metadataexists_operation);
+ } else if (sieve_command_is(tst, servermetadataexists_test)) {
+ sieve_operation_emit(cgenv->sblock, tst->ext,
+ &servermetadataexists_operation);
+ } else {
+ i_unreached();
+ }
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_metadataexists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ bool metadata = sieve_operation_is(denv->oprtn,
+ metadataexists_operation);
+
+ if (metadata)
+ sieve_code_dumpf(denv, "METADATAEXISTS");
+ else
+ sieve_code_dumpf(denv, "SERVERMETADATAEXISTS");
+
+ sieve_code_descend(denv);
+
+ if (metadata && !sieve_opr_string_dump(denv, address, "mailbox"))
+ return FALSE;
+
+ return sieve_opr_stringlist_dump(denv, address, "annotation-names");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_metadataexists_check_annotation(const struct sieve_runtime_env *renv,
+ struct imap_metadata_transaction *imtrans,
+ const char *mailbox, const char *aname,
+ bool *all_exist_r)
+{
+ struct mail_attribute_value avalue;
+ const char *error;
+ int ret;
+
+ if (!imap_metadata_verify_entry_name(aname, &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "%s test: "
+ "specified annotation name `%s' is invalid: %s",
+ (mailbox != NULL ?
+ "metadataexists" : "servermetadataexists"),
+ str_sanitize(aname, 256),
+ sieve_error_from_external(error));
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ ret = imap_metadata_get(imtrans, aname, &avalue);
+ if (ret < 0) {
+ enum mail_error error_code;
+ const char *error;
+
+ error = imap_metadata_transaction_get_last_error(
+ imtrans, &error_code);
+ sieve_runtime_error(
+ renv, NULL, "%s test: "
+ "failed to retrieve annotation `%s': %s%s",
+ (mailbox != NULL ?
+ "metadataexists" : "servermetadataexists"),
+ str_sanitize(aname, 256),
+ sieve_error_from_external(error),
+ (error_code == MAIL_ERROR_TEMP ?
+ " (temporary failure)" : ""));
+
+ *all_exist_r = FALSE;
+ return (error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ }
+ if (avalue.value == NULL && avalue.value_stream == NULL) {
+ sieve_runtime_trace(renv, 0,
+ "annotation `%s': not found", aname);
+ *all_exist_r = FALSE;
+ }
+
+ sieve_runtime_trace(renv, 0, "annotation `%s': found", aname);
+ return SIEVE_EXEC_OK;
+}
+
+static int
+tst_metadataexists_check_annotations(const struct sieve_runtime_env *renv,
+ const char *mailbox,
+ struct sieve_stringlist *anames,
+ bool *all_exist_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct mail_user *user = eenv->scriptenv->user;
+ struct mailbox *box = NULL;
+ struct imap_metadata_transaction *imtrans;
+ string_t *aname;
+ bool all_exist = TRUE;
+ int ret, sret, status;
+
+ *all_exist_r = FALSE;
+
+ if (user == NULL)
+ return SIEVE_EXEC_OK;
+
+ if (mailbox != NULL) {
+ struct mail_namespace *ns;
+ ns = mail_namespace_find(user->namespaces, mailbox);
+ box = mailbox_alloc(ns->list, mailbox, 0);
+ imtrans = imap_metadata_transaction_begin(box);
+ } else {
+ imtrans = imap_metadata_transaction_begin_server(user);
+ }
+
+ if (mailbox != NULL) {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "checking annotations of mailbox `%s':",
+ str_sanitize(mailbox, 80));
+ } else {
+ sieve_runtime_trace(
+ renv, SIEVE_TRLVL_TESTS,
+ "checking server annotations");
+ }
+
+ aname = NULL;
+ status = SIEVE_EXEC_OK;
+ while (all_exist &&
+ (sret = sieve_stringlist_next_item(anames, &aname)) > 0) {
+ ret = tst_metadataexists_check_annotation(
+ renv, imtrans, mailbox, str_c(aname), &all_exist);
+ if (ret <= 0) {
+ status = ret;
+ break;
+ }
+ }
+
+ if (sret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid annotation name stringlist item");
+ status = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
+ if (box != NULL)
+ mailbox_free(&box);
+
+ *all_exist_r = all_exist;
+ return status;
+}
+
+static int
+tst_metadataexists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ bool metadata = sieve_operation_is(renv->oprtn,
+ metadataexists_operation);
+ struct sieve_stringlist *anames;
+ string_t *mailbox;
+ bool trace = FALSE, all_exist = TRUE;
+ const char *error;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read mailbox */
+ if (metadata) {
+ ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* Read annotation names */
+ ret = sieve_opr_stringlist_read(renv, address, "annotation-names",
+ &anames);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (metadata &&
+ !sieve_mailbox_check_name(str_c(mailbox), &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "metadataexists test: "
+ "invalid mailbox name `%s' specified: %s",
+ str_sanitize(str_c(mailbox), 256), error);
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+ if (metadata) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "metadataexists test");
+ } else {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "servermetadataexists test");
+ }
+
+ sieve_runtime_trace_descend(renv);
+
+ trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ }
+
+ ret = tst_metadataexists_check_annotations(
+ renv, (metadata ? str_c(mailbox) : NULL), anames, &all_exist);
+ if (ret <= 0)
+ return ret;
+
+ if (trace) {
+ if (all_exist) {
+ sieve_runtime_trace(renv, 0,
+ "all annotations exist");
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "some annotations do not exist");
+ }
+ }
+
+ sieve_interpreter_set_test_result(renv->interp, all_exist);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/Makefile.am b/pigeonhole/src/lib-sieve/plugins/mime/Makefile.am
new file mode 100644
index 0000000..8ded659
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/Makefile.am
@@ -0,0 +1,30 @@
+noinst_LTLIBRARIES = libsieve_ext_mime.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-foreverypart.c \
+ cmd-break.c \
+ cmd-extracttext.c
+
+tags = \
+ tag-mime.c
+
+extensions = \
+ ext-mime.c \
+ ext-foreverypart.c \
+ ext-extracttext.c
+
+libsieve_ext_mime_la_SOURCES = \
+ ext-mime-common.c \
+ $(commands) \
+ $(tags) \
+ $(extensions)
+
+noinst_HEADERS = \
+ ext-mime-common.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in b/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in
new file mode 100644
index 0000000..cfe25ff
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in
@@ -0,0 +1,727 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/mime
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_mime_la_LIBADD =
+am__objects_1 = cmd-foreverypart.lo cmd-break.lo cmd-extracttext.lo
+am__objects_2 = tag-mime.lo
+am__objects_3 = ext-mime.lo ext-foreverypart.lo ext-extracttext.lo
+am_libsieve_ext_mime_la_OBJECTS = ext-mime-common.lo $(am__objects_1) \
+ $(am__objects_2) $(am__objects_3)
+libsieve_ext_mime_la_OBJECTS = $(am_libsieve_ext_mime_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-break.Plo \
+ ./$(DEPDIR)/cmd-extracttext.Plo \
+ ./$(DEPDIR)/cmd-foreverypart.Plo \
+ ./$(DEPDIR)/ext-extracttext.Plo \
+ ./$(DEPDIR)/ext-foreverypart.Plo \
+ ./$(DEPDIR)/ext-mime-common.Plo ./$(DEPDIR)/ext-mime.Plo \
+ ./$(DEPDIR)/tag-mime.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_mime_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_mime_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_mime.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ -I$(srcdir)/../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-foreverypart.c \
+ cmd-break.c \
+ cmd-extracttext.c
+
+tags = \
+ tag-mime.c
+
+extensions = \
+ ext-mime.c \
+ ext-foreverypart.c \
+ ext-extracttext.c
+
+libsieve_ext_mime_la_SOURCES = \
+ ext-mime-common.c \
+ $(commands) \
+ $(tags) \
+ $(extensions)
+
+noinst_HEADERS = \
+ ext-mime-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/mime/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/mime/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_mime.la: $(libsieve_ext_mime_la_OBJECTS) $(libsieve_ext_mime_la_DEPENDENCIES) $(EXTRA_libsieve_ext_mime_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_mime_la_OBJECTS) $(libsieve_ext_mime_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-break.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-extracttext.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-foreverypart.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-extracttext.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-foreverypart.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-mime-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-mime.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag-mime.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-break.Plo
+ -rm -f ./$(DEPDIR)/cmd-extracttext.Plo
+ -rm -f ./$(DEPDIR)/cmd-foreverypart.Plo
+ -rm -f ./$(DEPDIR)/ext-extracttext.Plo
+ -rm -f ./$(DEPDIR)/ext-foreverypart.Plo
+ -rm -f ./$(DEPDIR)/ext-mime-common.Plo
+ -rm -f ./$(DEPDIR)/ext-mime.Plo
+ -rm -f ./$(DEPDIR)/tag-mime.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-break.Plo
+ -rm -f ./$(DEPDIR)/cmd-extracttext.Plo
+ -rm -f ./$(DEPDIR)/cmd-foreverypart.Plo
+ -rm -f ./$(DEPDIR)/ext-extracttext.Plo
+ -rm -f ./$(DEPDIR)/ext-foreverypart.Plo
+ -rm -f ./$(DEPDIR)/ext-mime-common.Plo
+ -rm -f ./$(DEPDIR)/ext-mime.Plo
+ -rm -f ./$(DEPDIR)/tag-mime.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c b/pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c
new file mode 100644
index 0000000..e6ade4c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* break
+ *
+ * Syntax:
+ * break [":name" <name: string>]
+ *
+ */
+
+static bool cmd_break_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_break_pre_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_break_generate
+ (const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_break = {
+ .identifier = "break",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_break_registered,
+ .pre_validate = cmd_break_pre_validate,
+ .validate = cmd_break_validate,
+ .generate = cmd_break_generate,
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_break_validate_name_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def break_name_tag = {
+ .identifier = "name",
+ .validate = cmd_break_validate_name_tag
+};
+
+/*
+ * Break operation
+ */
+
+static bool cmd_break_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_break_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def break_operation = {
+ .mnemonic = "BREAK",
+ .ext_def = &foreverypart_extension,
+ .code = EXT_FOREVERYPART_OPERATION_BREAK,
+ .dump = cmd_break_operation_dump,
+ .execute = cmd_break_operation_execute
+};
+
+/*
+ * Validation data
+ */
+
+struct cmd_break_data {
+ struct sieve_ast_argument *name;
+ struct sieve_command *loop_cmd;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_break_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_break_data *data =
+ (struct cmd_break_data *)cmd->data;
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Check syntax:
+ * :name <string>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+ return FALSE;
+ data->name = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_break_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &break_name_tag, 0);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_break_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ struct cmd_break_data *data;
+ pool_t pool = sieve_command_pool(cmd);
+
+ data = p_new(pool, struct cmd_break_data, 1);
+ cmd->data = data;
+ return TRUE;
+}
+
+static bool cmd_break_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct cmd_break_data *data =
+ (struct cmd_break_data *)cmd->data;
+ struct sieve_ast_node *node = cmd->ast_node;
+ const char *name = ( data->name == NULL ?
+ NULL : sieve_ast_argument_strc(data->name) );
+
+ i_assert(node != NULL);
+ while ( node != NULL && node->command != NULL ) {
+ if ( sieve_command_is(node->command, cmd_foreverypart) ) {
+ struct ext_foreverypart_loop *loop =
+ (struct ext_foreverypart_loop *)node->command->data;
+ if ( name == NULL ||
+ (name != NULL && loop->name != NULL &&
+ strcmp(name, loop->name) == 0) ) {
+ data->loop_cmd = node->command;
+ break;
+ }
+ }
+ node = sieve_ast_node_parent(node);
+ }
+
+ if ( data->loop_cmd == NULL ) {
+ if ( name == NULL ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the break command is not placed inside "
+ "a foreverypart loop");
+ } else {
+ sieve_command_validate_error(valdtr, cmd,
+ "the break command is not placed inside "
+ "a foreverypart loop named `%s'",
+ name);
+ }
+ return FALSE;
+ }
+
+ sieve_command_exit_block_unconditionally(cmd);
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_break_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct cmd_break_data *data =
+ (struct cmd_break_data *)cmd->data;
+ struct ext_foreverypart_loop *loop;
+
+ i_assert( data->loop_cmd != NULL );
+ loop = (struct ext_foreverypart_loop *)data->loop_cmd->data;
+
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &break_operation);
+ sieve_jumplist_add(loop->exit_jumps,
+ sieve_binary_emit_offset(cgenv->sblock, 0));
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_break_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+
+ sieve_code_dumpf(denv, "BREAK");
+ sieve_code_descend(denv);
+
+ if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_break_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_interpreter_loop *loop;
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+ sieve_size_t loop_end;
+
+ /*
+ * Read operands
+ */
+
+ if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+ {
+ sieve_runtime_trace_error(renv, "invalid loop end offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ loop_end = pc + offset;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "break command");
+ sieve_runtime_trace_descend(renv);
+
+ loop = sieve_interpreter_loop_get
+ (renv->interp, loop_end, &foreverypart_extension);
+ if ( loop == NULL ) {
+ sieve_runtime_trace_error(renv, "no matching loop found");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ sieve_interpreter_loop_break(renv->interp, loop);
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/cmd-extracttext.c b/pigeonhole/src/lib-sieve/plugins/mime/cmd-extracttext.c
new file mode 100644
index 0000000..d9b31a2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/cmd-extracttext.c
@@ -0,0 +1,370 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extracttext command
+ *
+ * Syntax:
+ * extracttext [MODIFIER] [":first" number] <varname: string>
+ */
+
+static bool cmd_extracttext_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_extracttext_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_extracttext_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_extracttext = {
+ .identifier = "extracttext",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_extracttext_registered,
+ .validate = cmd_extracttext_validate,
+ .generate = cmd_extracttext_generate
+};
+
+/*
+ * Extracttext command tags
+ */
+
+enum cmd_extracttext_optional {
+ CMD_EXTRACTTEXT_OPT_END,
+ CMD_EXTRACTTEXT_OPT_FIRST
+};
+
+static bool cmd_extracttext_validate_first_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def extracttext_from_tag = {
+ .identifier = "first",
+ .validate = cmd_extracttext_validate_first_tag
+};
+
+/*
+ * Extracttext operation
+ */
+
+static bool cmd_extracttext_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_extracttext_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def extracttext_operation = {
+ .mnemonic = "EXTRACTTEXT",
+ .ext_def = &extracttext_extension,
+ .dump = cmd_extracttext_operation_dump,
+ .execute = cmd_extracttext_operation_execute
+};
+
+/*
+ * Compiler context
+ */
+
+struct cmd_extracttext_context {
+ ARRAY_TYPE(sieve_variables_modifier) modifiers;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_extracttext_validate_first_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :first <number>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) )
+ return FALSE;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/* Command registration */
+
+static bool cmd_extracttext_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ struct ext_extracttext_context *ectx =
+ (struct ext_extracttext_context *)ext->context;
+
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &extracttext_from_tag, CMD_EXTRACTTEXT_OPT_FIRST);
+ sieve_variables_modifiers_link_tag
+ (valdtr, ectx->var_ext, cmd_reg);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_extracttext_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct ext_extracttext_context *ectx =
+ (struct ext_extracttext_context *)this_ext->context;
+ struct sieve_ast_node *node = cmd->ast_node;
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ pool_t pool = sieve_command_pool(cmd);
+ struct cmd_extracttext_context *sctx;
+
+ /* Create command context */
+ sctx = p_new(pool, struct cmd_extracttext_context, 1);
+ p_array_init(&sctx->modifiers, pool, 4);
+ cmd->data = (void *) sctx;
+
+ /* Validate modifiers */
+ if ( !sieve_variables_modifiers_validate
+ (valdtr, cmd, &sctx->modifiers) )
+ return FALSE;
+
+ /* Validate varname argument */
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "varname", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+ if ( !sieve_variable_argument_activate
+ (ectx->var_ext, ectx->var_ext, valdtr, cmd, arg, TRUE) )
+ return FALSE;
+
+ /* Check foreverypart context */
+ i_assert(node != NULL);
+ while ( node != NULL ) {
+ if ( node->command != NULL &&
+ sieve_command_is(node->command, cmd_foreverypart) )
+ break;
+ node = sieve_ast_node_parent(node);
+ }
+
+ if ( node == NULL ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the extracttext command is not placed inside "
+ "a foreverypart loop");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_extracttext_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ struct cmd_extracttext_context *sctx =
+ (struct cmd_extracttext_context *) cmd->data;
+
+ sieve_operation_emit(sblock, this_ext, &extracttext_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ /* Generate modifiers */
+ if ( !sieve_variables_modifiers_generate
+ (cgenv, &sctx->modifiers) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_extracttext_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "EXTRACTTEXT");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case CMD_EXTRACTTEXT_OPT_FIRST:
+ opok = sieve_opr_number_dump(denv, address, "first");
+ break;
+ default:
+ return FALSE;
+ }
+ if ( !opok ) return FALSE;
+ }
+
+ /* Print both variable name and string value */
+ if ( !sieve_opr_string_dump(denv, address, "varname") )
+ return FALSE;
+
+ return sieve_variables_modifiers_code_dump(denv, address);
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_extracttext_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_extracttext_context *ectx =
+ (struct ext_extracttext_context *)this_ext->context;
+ struct sieve_variable_storage *storage;
+ ARRAY_TYPE(sieve_variables_modifier) modifiers;
+ struct ext_foreverypart_runtime_loop *sfploop;
+ struct sieve_message_part *mpart;
+ struct sieve_message_part_data mpart_data;
+ int opt_code = 0;
+ sieve_number_t first = 0;
+ string_t *value;
+ unsigned int var_index;
+ bool have_first = FALSE;
+ int ret = SIEVE_EXEC_OK;
+
+ /*
+ * Read the normal operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read
+ (renv, address, &opt_code)) < 0 )
+ return SIEVE_EXEC_BIN_CORRUPT;
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case CMD_EXTRACTTEXT_OPT_FIRST:
+ ret = sieve_opr_number_read
+ (renv, address, "first", &first);
+ have_first = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ if ( ret <= 0 ) return ret;
+ }
+
+ /* Varname operand */
+
+ if ( (ret=sieve_variable_operand_read
+ (renv, address, "varname", &storage, &var_index)) <= 0 )
+ return ret;
+
+ /* Modifiers */
+
+ if ( (ret=sieve_variables_modifiers_code_read
+ (renv, ectx->var_ext, address, &modifiers)) <= 0 )
+ return ret;
+
+ /*
+ * Determine and assign the value
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "extracttext command");
+ sieve_runtime_trace_descend(renv);
+
+ sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+ if ( sfploop == NULL ) {
+ sieve_runtime_trace_error(renv,
+ "outside foreverypart context");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Get current message part */
+ mpart = sieve_message_part_iter_current(&sfploop->part_iter);
+ i_assert( mpart != NULL );
+
+ /* Get message part content */
+ sieve_message_part_get_data(mpart, &mpart_data, TRUE);
+
+ /* Apply ":first" limit, if any */
+ if ( !have_first || (size_t)first > mpart_data.size ) {
+ value = t_str_new_const(mpart_data.content, mpart_data.size);
+ } else {
+ value = t_str_new((size_t)first);
+ str_append_data(value, mpart_data.content, (size_t)first);
+ }
+
+ /* Apply modifiers */
+ if ( (ret=sieve_variables_modifiers_apply
+ (renv, ectx->var_ext, &modifiers, &value)) <= 0 )
+ return ret;
+
+ /* Actually assign the value if all is well */
+ i_assert ( value != NULL );
+ if ( !sieve_variable_assign(storage, var_index, value) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ /* Trace */
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ const char *var_name, *var_id;
+
+ (void)sieve_variable_get_identifier(storage, var_index, &var_name);
+ var_id = sieve_variable_get_varid(storage, var_index);
+
+ sieve_runtime_trace_here(renv, 0, "assign `%s' [%s] = \"%s\"",
+ var_name, var_id, str_c(value));
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/cmd-foreverypart.c b/pigeonhole/src/lib-sieve/plugins/mime/cmd-foreverypart.c
new file mode 100644
index 0000000..435cb5c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/cmd-foreverypart.c
@@ -0,0 +1,377 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-message.h"
+
+#include "ext-mime-common.h"
+
+#include <ctype.h>
+
+/* Foreverypart
+ *
+ * Syntax:
+ * foreverypart [":name" <name: string>] <block>
+ *
+ */
+
+static bool cmd_foreverypart_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_foreverypart_pre_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_foreverypart_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_foreverypart_generate
+ (const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_foreverypart = {
+ .identifier = "foreverypart",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = TRUE,
+ .block_required = TRUE,
+ .registered = cmd_foreverypart_registered,
+ .pre_validate = cmd_foreverypart_pre_validate,
+ .validate = cmd_foreverypart_validate,
+ .generate = cmd_foreverypart_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool cmd_foreverypart_validate_name_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def foreverypart_name_tag = {
+ .identifier = "name",
+ .validate = cmd_foreverypart_validate_name_tag,
+};
+
+/*
+ * foreverypart operation
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_begin_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_begin_operation = {
+ .mnemonic = "FOREVERYPART_BEGIN",
+ .ext_def = &foreverypart_extension,
+ .code = EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+ .dump = cmd_foreverypart_begin_operation_dump,
+ .execute = cmd_foreverypart_begin_operation_execute
+};
+
+static bool cmd_foreverypart_end_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_foreverypart_end_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def foreverypart_end_operation = {
+ .mnemonic = "FOREVERYPART_END",
+ .ext_def = &foreverypart_extension,
+ .code = EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+ .dump = cmd_foreverypart_end_operation_dump,
+ .execute = cmd_foreverypart_end_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_foreverypart_validate_name_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct ext_foreverypart_loop *loop =
+ (struct ext_foreverypart_loop *)cmd->data;
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Check syntax:
+ * :name <string>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, TRUE) )
+ return FALSE;
+ loop->name = sieve_ast_argument_strc(*arg);
+
+ /* Detach parameter */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_foreverypart_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &foreverypart_name_tag, 0);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_foreverypart_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ struct ext_foreverypart_loop *loop;
+ pool_t pool = sieve_command_pool(cmd);
+
+ loop = p_new(pool, struct ext_foreverypart_loop, 1);
+ cmd->data = loop;
+
+ return TRUE;
+}
+
+static bool cmd_foreverypart_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_node *node = cmd->ast_node;
+ unsigned int nesting = 0;
+
+ /* Determine nesting depth of foreverypart commands at this point. */
+ i_assert(node != NULL);
+ node = sieve_ast_node_parent(node);
+ while ( node != NULL && node->command != NULL ) {
+ if ( sieve_command_is(node->command, cmd_foreverypart) )
+ nesting++;
+ node = sieve_ast_node_parent(node);
+ }
+
+ /* Enforce nesting limit
+ NOTE: this only recognizes the foreverypart command as a loop; if
+ new loop commands are introduced in the future, these must be
+ recognized somehow. */
+ if ( nesting + 1 > SIEVE_MAX_LOOP_DEPTH ) {
+ sieve_command_validate_error(valdtr, cmd,
+ "the nested foreverypart loop exceeds "
+ "the nesting limit (<= %u levels)",
+ SIEVE_MAX_LOOP_DEPTH);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_foreverypart_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ struct ext_foreverypart_loop *loop =
+ (struct ext_foreverypart_loop *)cmd->data;
+ sieve_size_t block_begin, loop_jump;
+
+ /* Emit FOREVERYPART_BEGIN operation */
+ sieve_operation_emit(cgenv->sblock,
+ cmd->ext, &foreverypart_begin_operation);
+
+ /* Emit exit address */
+ loop->exit_jumps = sieve_jumplist_create
+ (sieve_command_pool(cmd), cgenv->sblock);
+ sieve_jumplist_add(loop->exit_jumps,
+ sieve_binary_emit_offset(cgenv->sblock, 0));
+ block_begin = sieve_binary_block_get_size(cgenv->sblock);
+
+ /* Generate loop block */
+ if ( !sieve_generate_block(cgenv, cmd->ast_node) )
+ return FALSE;
+
+ /* Emit FOREVERYPART_END operation */
+ sieve_operation_emit(cgenv->sblock,
+ cmd->ext, &foreverypart_end_operation);
+ loop_jump = sieve_binary_block_get_size(cgenv->sblock);
+ i_assert(loop_jump > block_begin);
+ (void)sieve_binary_emit_offset
+ (cgenv->sblock, (loop_jump - block_begin));
+
+ /* Resolve exit address */
+ sieve_jumplist_resolve(loop->exit_jumps);
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_foreverypart_begin_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+
+ sieve_code_dumpf(denv, "FOREVERYPART_BEGIN");
+ sieve_code_descend(denv);
+
+ if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
+ return TRUE;
+}
+
+static bool cmd_foreverypart_end_operation_dump
+(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+
+ sieve_code_dumpf(denv, "FOREVERYPART_END");
+ sieve_code_descend(denv);
+
+ if ( !sieve_binary_read_offset(denv->sblock, address, &offset) )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "BEGIN: -%d [%08x]", offset, pc - offset);
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_foreverypart_begin_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_interpreter_loop *loop;
+ struct ext_foreverypart_runtime_loop *fploop, *sfploop;
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+ sieve_size_t loop_end;
+ pool_t pool;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+ {
+ sieve_runtime_trace_error(renv, "invalid loop end offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ loop_end = pc + offset;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "foreverypart loop begin");
+ sieve_runtime_trace_descend(renv);
+
+ sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+
+ if ( (ret=sieve_interpreter_loop_start(renv->interp,
+ loop_end, &foreverypart_extension, &loop)) <= 0 )
+ return ret;
+
+ pool = sieve_interpreter_loop_get_pool(loop);
+ fploop = p_new(pool, struct ext_foreverypart_runtime_loop, 1);
+
+ if ( sfploop == NULL ) {
+ if ( (ret=sieve_message_part_iter_init
+ (&fploop->part_iter, renv)) <= 0 )
+ return ret;
+ } else {
+ sieve_message_part_iter_children(&sfploop->part_iter,
+ &fploop->part_iter);
+ }
+ fploop->part = sieve_message_part_iter_current(&fploop->part_iter);
+ if (fploop->part != NULL) {
+ sieve_interpreter_loop_set_context(loop, (void*)fploop);
+ } else {
+ /* No children parts to iterate */
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
+ "no children at this level");
+ sieve_interpreter_loop_break(renv->interp, loop);
+ }
+ return SIEVE_EXEC_OK;
+}
+
+static int cmd_foreverypart_end_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_interpreter_loop *loop;
+ struct ext_foreverypart_runtime_loop *fploop;
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+ sieve_size_t loop_begin;
+
+ /*
+ * Read operands
+ */
+
+ if ( !sieve_binary_read_offset(renv->sblock, address, &offset) )
+ {
+ sieve_runtime_trace_error(renv, "invalid loop begin offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ loop_begin = pc - offset;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv,
+ SIEVE_TRLVL_COMMANDS, "foreverypart loop end");
+ sieve_runtime_trace_descend(renv);
+
+ loop = sieve_interpreter_loop_get
+ (renv->interp, *address, &foreverypart_extension);
+ if ( loop == NULL ) {
+ sieve_runtime_trace_error(renv, "no matching loop found");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ fploop = (struct ext_foreverypart_runtime_loop *)
+ sieve_interpreter_loop_get_context(loop);
+ i_assert(fploop->part != NULL);
+ fploop->part = sieve_message_part_iter_next(&fploop->part_iter);
+ if ( fploop->part == NULL ) {
+ sieve_runtime_trace(renv,
+ SIEVE_TRLVL_COMMANDS, "no more message parts");
+ return sieve_interpreter_loop_break(renv->interp, loop);
+ }
+
+ sieve_runtime_trace(renv,
+ SIEVE_TRLVL_COMMANDS, "switched to next message part");
+ return sieve_interpreter_loop_next(renv->interp, loop, loop_begin);
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/ext-extracttext.c b/pigeonhole/src/lib-sieve/plugins/mime/ext-extracttext.c
new file mode 100644
index 0000000..4eab76b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/ext-extracttext.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension extracttext
+ * ---------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 7
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_extracttext_load
+ (const struct sieve_extension *ext, void **context);
+static void ext_extracttext_unload
+ (const struct sieve_extension *ext);
+static bool ext_extracttext_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def extracttext_extension = {
+ .name = "extracttext",
+ .load = ext_extracttext_load,
+ .unload = ext_extracttext_unload,
+ .validator_load = ext_extracttext_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(extracttext_operation)
+};
+
+static bool ext_extracttext_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_extracttext_context *ectx;
+
+ if ( *context != NULL )
+ ext_extracttext_unload(ext);
+
+ ectx = i_new(struct ext_extracttext_context, 1);
+ ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+ ectx->fep_ext = sieve_extension_register
+ (svinst, &foreverypart_extension, FALSE);
+ *context = (void *)ectx;
+ return TRUE;
+}
+
+static void ext_extracttext_unload
+(const struct sieve_extension *ext)
+{
+ struct ext_extracttext_context *ctx =
+ (struct ext_extracttext_context *) ext->context;
+
+ i_free(ctx);
+}
+
+/*
+ * Extension validation
+ */
+
+static bool ext_extracttext_validator_validate
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+
+const struct sieve_validator_extension
+extracttext_validator_extension = {
+ .ext = &extracttext_extension,
+ .validate = ext_extracttext_validator_validate
+};
+
+static bool ext_extracttext_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register validator extension to check for conflict with eextracttext */
+ sieve_validator_extension_register
+ (valdtr, ext, &extracttext_validator_extension, NULL);
+
+ /* Register new commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_extracttext);
+
+ return TRUE;
+}
+
+static bool ext_extracttext_validator_validate
+(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ bool required ATTR_UNUSED)
+{
+ struct ext_extracttext_context *ectx =
+ (struct ext_extracttext_context *)ext->context;
+
+ if ( ectx->var_ext == NULL ||
+ !sieve_ext_variables_is_active
+ (ectx->var_ext, valdtr) ) {
+ sieve_argument_validate_error(valdtr, require_arg,
+ "extracttext extension cannot be used "
+ "without variables extension");
+ return FALSE;
+ }
+ if ( ectx->fep_ext == NULL ||
+ !sieve_validator_extension_loaded
+ (valdtr, ectx->fep_ext) ) {
+ sieve_argument_validate_error(valdtr, require_arg,
+ "extracttext extension cannot be used "
+ "without foreverypart extension");
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/ext-foreverypart.c b/pigeonhole/src/lib-sieve/plugins/mime/ext-foreverypart.c
new file mode 100644
index 0000000..6bca199
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/ext-foreverypart.c
@@ -0,0 +1,62 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension foreverypart
+ * ----------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 3
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_foreverypart_operations[] = {
+ &foreverypart_begin_operation,
+ &foreverypart_end_operation,
+ &break_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_foreverypart_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def foreverypart_extension = {
+ .name = "foreverypart",
+ .validator_load = ext_foreverypart_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_foreverypart_operations)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_foreverypart_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_foreverypart);
+ sieve_validator_register_command(valdtr, ext, &cmd_break);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.c b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.c
new file mode 100644
index 0000000..5b38bcb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.c
@@ -0,0 +1,27 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-interpreter.h"
+
+#include "ext-mime-common.h"
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv)
+{
+ struct sieve_interpreter_loop *loop;
+ struct ext_foreverypart_runtime_loop *fploop;
+
+ loop = sieve_interpreter_loop_get_global
+ (renv->interp, NULL, &foreverypart_extension);
+ if ( loop == NULL ) {
+ fploop = NULL;
+ } else {
+ fploop = (struct ext_foreverypart_runtime_loop *)
+ sieve_interpreter_loop_get_context(loop);
+ i_assert(fploop->part != NULL);
+ }
+
+ return fploop;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.h b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.h
new file mode 100644
index 0000000..8b0054d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime-common.h
@@ -0,0 +1,85 @@
+#ifndef EXT_FOREVERYPART_COMMON_H
+#define EXT_FOREVERYPART_COMMON_H
+
+#include "sieve-message.h"
+
+/*
+ * Extension
+ */
+
+struct ext_extracttext_context {
+ const struct sieve_extension *var_ext;
+ const struct sieve_extension *fep_ext;
+};
+
+extern const struct sieve_extension_def foreverypart_extension;
+extern const struct sieve_extension_def mime_extension;
+extern const struct sieve_extension_def extracttext_extension;
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def mime_tag;
+extern const struct sieve_argument_def mime_anychild_tag;
+extern const struct sieve_argument_def mime_type_tag;
+extern const struct sieve_argument_def mime_subtype_tag;
+extern const struct sieve_argument_def mime_contenttype_tag;
+extern const struct sieve_argument_def mime_param_tag;
+
+/*
+ * Commands
+ */
+
+struct ext_foreverypart_loop {
+ const char *name;
+ struct sieve_jumplist *exit_jumps;
+};
+
+extern const struct sieve_command_def cmd_foreverypart;
+extern const struct sieve_command_def cmd_break;
+extern const struct sieve_command_def cmd_extracttext;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def foreverypart_begin_operation;
+extern const struct sieve_operation_def foreverypart_end_operation;
+extern const struct sieve_operation_def break_operation;
+extern const struct sieve_operation_def extracttext_operation;
+
+enum ext_foreverypart_opcode {
+ EXT_FOREVERYPART_OPERATION_FOREVERYPART_BEGIN,
+ EXT_FOREVERYPART_OPERATION_FOREVERYPART_END,
+ EXT_FOREVERYPART_OPERATION_BREAK,
+};
+
+/*
+ * Operands
+ */
+
+enum ext_mime_option {
+ EXT_MIME_OPTION_NONE = 0,
+ EXT_MIME_OPTION_TYPE,
+ EXT_MIME_OPTION_SUBTYPE,
+ EXT_MIME_OPTION_CONTENTTYPE,
+ EXT_MIME_OPTION_PARAM
+};
+
+extern const struct sieve_operand_def mime_operand;
+
+/*
+ * Foreverypart loop
+ */
+
+struct ext_foreverypart_runtime_loop {
+ struct sieve_message_part_iter part_iter;
+ struct sieve_message_part *part;
+};
+
+struct ext_foreverypart_runtime_loop *
+ext_foreverypart_runtime_loop_get_current
+(const struct sieve_runtime_env *renv);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/ext-mime.c b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime.c
new file mode 100644
index 0000000..da9963f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/ext-mime.c
@@ -0,0 +1,77 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension mime
+ * --------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5703, Section 4
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_mime_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def mime_extension = {
+ .name = "mime",
+ .validator_load = ext_mime_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(mime_operand)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_mime_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register :mime tag and friends with header, address and exists test
+ * commands and we don't care whether these command are registered or
+ * even whether these will be registered at all. The validator handles
+ * either situation gracefully.
+ */
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_anychild_tag, 0);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_type_tag, 0);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_subtype_tag, 0);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_contenttype_tag, 0);
+ sieve_validator_register_external_tag
+ (valdtr, "header", ext, &mime_param_tag, 0);
+
+ sieve_validator_register_external_tag
+ (valdtr, "address", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "address", ext, &mime_anychild_tag, 0);
+
+ sieve_validator_register_external_tag
+ (valdtr, "exists", ext, &mime_tag, SIEVE_OPT_MESSAGE_OVERRIDE);
+ sieve_validator_register_external_tag
+ (valdtr, "exists", ext, &mime_anychild_tag, 0);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/mime/tag-mime.c b/pigeonhole/src/lib-sieve/plugins/mime/tag-mime.c
new file mode 100644
index 0000000..379365e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/mime/tag-mime.c
@@ -0,0 +1,757 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "rfc822-parser.h"
+#include "rfc2231-parser.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+#include "ext-mime-common.h"
+
+/*
+ * Tagged argument
+ */
+
+static bool tag_mime_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_mime_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def mime_tag = {
+ .identifier = "mime",
+ .validate = tag_mime_validate,
+ .generate = tag_mime_generate
+};
+
+static bool tag_mime_option_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+const struct sieve_argument_def mime_anychild_tag = {
+ .identifier = "anychild",
+ .validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_type_tag = {
+ .identifier = "type",
+ .validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_subtype_tag = {
+ .identifier = "subtype",
+ .validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_contenttype_tag = {
+ .identifier = "contenttype",
+ .validate = tag_mime_option_validate
+};
+
+const struct sieve_argument_def mime_param_tag = {
+ .identifier = "param",
+ .validate = tag_mime_option_validate
+};
+
+/*
+ * Header override
+ */
+
+static bool svmo_mime_dump_context
+ (const struct sieve_message_override *svmo,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int svmo_mime_read_context
+ (const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv, sieve_size_t *address,
+ void **ho_context);
+static int svmo_mime_header_override
+ (const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv,
+ bool mime_decode, struct sieve_stringlist **headers);
+
+const struct sieve_message_override_def mime_header_override = {
+ SIEVE_OBJECT("mime", &mime_operand, 0),
+ .sequence = 0, /* Completely replace header source */
+ .dump_context = svmo_mime_dump_context,
+ .read_context = svmo_mime_read_context,
+ .header_override = svmo_mime_header_override
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_header_overrides =
+ SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(mime_header_override);
+
+const struct sieve_operand_def mime_operand = {
+ .name = "mime operand",
+ .ext_def = &mime_extension,
+ .class = &sieve_message_override_operand_class,
+ .interface = &ext_header_overrides
+};
+
+/*
+ * Tag data
+ */
+
+struct tag_mime_data {
+ enum ext_mime_option mimeopt;
+ struct sieve_ast_argument *param_arg;
+ bool anychild:1;
+};
+
+/*
+ * Tag validation
+ */
+
+static struct tag_mime_data *
+tag_mime_get_data(struct sieve_command *cmd,
+ struct sieve_ast_argument *tag)
+{
+ struct tag_mime_data *data;
+
+ if (tag->argument->data == NULL) {
+ data = p_new(sieve_command_pool(cmd), struct tag_mime_data, 1);
+ tag->argument->data = (void *)data;
+ } else {
+ data = (struct tag_mime_data *)tag->argument->data;
+ }
+
+ return data;
+}
+
+static bool tag_mime_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Skip the tag itself */
+ *arg = sieve_ast_argument_next(*arg);
+
+ (void)tag_mime_get_data(cmd, tag);
+ return TRUE;
+}
+
+static bool tag_mime_option_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct sieve_ast_argument *mime_arg;
+ struct tag_mime_data *data;
+
+ i_assert(tag != NULL);
+
+ /* Detach tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Find required ":mime" tag */
+ mime_arg = sieve_command_find_argument(cmd, &mime_tag);
+ if ( mime_arg == NULL ) {
+ sieve_argument_validate_error(valdtr, tag,
+ "the :%s tag for the %s %s cannot be specified "
+ "without the :mime tag", sieve_ast_argument_tag(tag),
+ sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ /* Annotate ":mime" tag with the data provided by this option tag */
+ data = tag_mime_get_data(cmd, mime_arg);
+ if ( sieve_argument_is(tag, mime_anychild_tag) )
+ data->anychild = TRUE;
+ else {
+ if ( data->mimeopt != EXT_MIME_OPTION_NONE ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "the :type, :subtype, :contenttype, and :param "
+ "arguments for the %s test are mutually exclusive, "
+ "but more than one was specified",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+ if ( sieve_argument_is(tag, mime_type_tag) )
+ data->mimeopt = EXT_MIME_OPTION_TYPE;
+ else if ( sieve_argument_is(tag, mime_subtype_tag) )
+ data->mimeopt = EXT_MIME_OPTION_SUBTYPE;
+ else if ( sieve_argument_is(tag, mime_contenttype_tag) )
+ data->mimeopt = EXT_MIME_OPTION_CONTENTTYPE;
+ else if ( sieve_argument_is(tag, mime_param_tag) ) {
+ /* Check syntax:
+ * ":param" <param-list: string-list>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE) ) {
+ return FALSE;
+ }
+
+ data->mimeopt = EXT_MIME_OPTION_PARAM;
+ data->param_arg = *arg;
+
+ /* Detach parameter */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+ } else
+ i_unreached();
+ }
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tag_mime_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ struct tag_mime_data *data =
+ (struct tag_mime_data *)arg->argument->data;
+
+ if ( sieve_ast_argument_type(arg) != SAAT_TAG )
+ return FALSE;
+
+ sieve_opr_message_override_emit
+ (cgenv->sblock, arg->argument->ext, &mime_header_override);
+
+ (void)sieve_binary_emit_byte
+ (cgenv->sblock, ( data->anychild ? 1 : 0 ));
+ (void)sieve_binary_emit_byte
+ (cgenv->sblock, data->mimeopt);
+ if ( data->mimeopt == EXT_MIME_OPTION_PARAM &&
+ !sieve_generate_argument(cgenv, data->param_arg, cmd) )
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Content-type stringlist
+ */
+
+enum content_type_part {
+ CONTENT_TYPE_PART_NONE = 0,
+ CONTENT_TYPE_PART_TYPE,
+ CONTENT_TYPE_PART_SUBTYPE,
+ CONTENT_TYPE_PART_CONTENTTYPE,
+};
+
+/* Object */
+
+static int content_header_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void content_header_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int content_header_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+static void content_header_stringlist_set_trace
+ (struct sieve_stringlist *strlist, bool trace);
+
+struct content_header_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_header_list *source;
+
+ enum ext_mime_option option;
+ const char *const *params;
+
+ const char *const *param_values;
+};
+
+static struct sieve_stringlist *content_header_stringlist_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_header_list *source,
+ enum ext_mime_option option, const char *const *params)
+{
+ struct content_header_stringlist *strlist;
+
+ strlist = t_new(struct content_header_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = content_header_stringlist_next_item;
+ strlist->strlist.reset = content_header_stringlist_reset;
+ strlist->strlist.set_trace = content_header_stringlist_set_trace;
+ strlist->source = source;
+ strlist->option = option;
+ strlist->params = params;
+
+ if ( option != EXT_MIME_OPTION_PARAM ) {
+ /* One header can have multiple parameters, so we cannot rely
+ on the source length for the :param option. */
+ strlist->strlist.get_length = content_header_stringlist_get_length;
+ }
+
+ return &strlist->strlist;
+}
+
+/* Implementation */
+
+static inline int _decode_hex_digit(const unsigned char digit)
+{
+ switch ( digit ) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return digit - '0';
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ return digit - 'a' + 0x0a;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ return digit - 'A' + 0x0A;
+ }
+ return -1;
+}
+
+static string_t *
+content_type_param_decode(const char *value)
+{
+ const unsigned char *p, *plast;
+
+ string_t *str = t_str_new(64);
+ plast = p = (const unsigned char *)value;
+ while ( *p != '\0' ) {
+ unsigned char ch;
+ int digit;
+
+ if ( *p == '%' ) {
+ if ( p - plast > 0 )
+ str_append_data(str, plast, (p - plast));
+ p++;
+ if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+ return NULL;
+ ch = (unsigned char)digit;
+ p++;
+ if ( *p == '\0' || (digit=_decode_hex_digit(*p)) < 0 )
+ return NULL;
+ ch = (ch << 4) + (unsigned char)digit;
+ str_append_data(str, &ch, 1);
+ plast = p + 1;
+ }
+ p++;
+ }
+ if ( p - plast > 0 )
+ str_append_data(str, plast, (p - plast));
+ return str;
+}
+
+static string_t *
+content_type_param_next(struct content_header_stringlist *strlist)
+{
+ const struct sieve_runtime_env *renv = strlist->strlist.runenv;
+ const char *const *values = strlist->param_values;
+ bool trace = strlist->strlist.trace;
+
+ i_assert( strlist->params != NULL );
+
+ /* Iterate over all parsed parameter values */
+ for ( ; *values != NULL; values += 2 ) {
+ const char *const *params = strlist->params;
+ const char *name = values[0], *value = values[1];
+ size_t nlen = strlen(name);
+
+ /* Iterate over all interesting parameter names */
+ for ( ; *params != NULL; params++ ) {
+ size_t plen = strlen(*params);
+
+ if ( plen != nlen &&
+ (nlen != plen + 1 || name[nlen-1] != '*') )
+ continue;
+
+ if ( plen == nlen ) {
+ if ( strcasecmp(name, *params) == 0 ) {
+ /* Return raw value */
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "found mime parameter `%s' in mime header",
+ *params);
+ }
+
+ strlist->param_values = values + 2;
+ return t_str_new_const(value, strlen(value));
+ }
+ } else {
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "found encoded parameter `%s' in mime header",
+ *params);
+ }
+
+ if ( strncasecmp(name, *params, plen) == 0 ) {
+ string_t *result = NULL;
+
+ strlist->param_values = values + 2;
+
+ /* Decode value first */
+ // FIXME: transcode charset
+ value = strchr(value, '\'');
+ if (value != NULL)
+ value = strchr(value+1, '\'');
+ if (value != NULL)
+ result = content_type_param_decode(value + 1);
+ if (result == NULL)
+ strlist->param_values = NULL;
+ return result;
+ }
+ }
+ }
+ }
+
+ strlist->param_values = NULL;
+ return NULL;
+}
+
+// FIXME: not too happy with the use of string_t like this.
+// Sieve should have a special runtime string type (TODO)
+static string_t *
+content_header_parse(struct content_header_stringlist *strlist,
+ const char *hdr_name, string_t *str)
+{
+ const struct sieve_runtime_env *renv = strlist->strlist.runenv;
+ bool trace = strlist->strlist.trace;
+ struct rfc822_parser_context parser;
+ const char *type, *p;
+ bool is_ctype = FALSE;
+ string_t *content;
+
+ if ( strlist->option == EXT_MIME_OPTION_NONE )
+ return str;
+
+ if ( strcasecmp(hdr_name, "content-type") == 0 )
+ is_ctype = TRUE;
+ else if ( strcasecmp(hdr_name, "content-disposition") != 0 ) {
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "non-mime header yields empty string");
+ }
+ return t_str_new(0);
+ }
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, str_data(str), str_len(str), NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type/disposition */
+ content = t_str_new(64);
+ if ( is_ctype ){
+ if (rfc822_parse_content_type(&parser, content) < 0) {
+ str_truncate(content, 0);
+ return content;
+ }
+ } else {
+ if (rfc822_parse_mime_token(&parser, content) < 0) {
+ str_truncate(content, 0);
+ return content;
+ }
+ }
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end && *parser.data != ';' ) {
+ str_truncate(content, 0);
+ return content;
+ }
+
+ if ( strlist->option == EXT_MIME_OPTION_PARAM ) {
+ string_t *param_val;
+
+ /* MIME parameter */
+ i_assert( strlist->params != NULL );
+
+ // FIXME: not very nice when multiple parameters in the same header
+ // are queried in successive tests.
+ str_truncate(content, 0);
+ rfc2231_parse(&parser, &strlist->param_values);
+
+ param_val = content_type_param_next(strlist);
+ if ( param_val != NULL )
+ content = param_val;
+ } else {
+ /* Get :type/:subtype:/:contenttype value */
+ type = str_c(content);
+ p = strchr(type, '/');
+ switch ( strlist->option ) {
+ case EXT_MIME_OPTION_TYPE:
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracted MIME type");
+ }
+ if ( p != NULL ) {
+ i_assert( is_ctype );
+ str_truncate(content, (p - type));
+ }
+ break;
+ case EXT_MIME_OPTION_SUBTYPE:
+ if ( p == NULL ) {
+ i_assert( !is_ctype );
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "no MIME sub-type for content-disposition");
+ }
+ str_truncate(content, 0);
+ break;
+ }
+
+ i_assert( is_ctype );
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracted MIME sub-type");
+ }
+ str_delete(content, 0, (p - type) + 1);
+ break;
+ case EXT_MIME_OPTION_CONTENTTYPE:
+ sieve_runtime_trace(renv, 0,
+ "extracted full MIME contenttype");
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Success */
+ return content;
+}
+
+static int content_header_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct content_header_stringlist *strlist =
+ (struct content_header_stringlist *)_strlist;
+ const char *hdr_name;
+ int ret;
+
+ if ( strlist->param_values != NULL ) {
+ string_t *param_val;
+
+ i_assert( strlist->option == EXT_MIME_OPTION_PARAM );
+ param_val = content_type_param_next(strlist);
+ if ( param_val != NULL ) {
+ *str_r = param_val;
+ return 1;
+ }
+ }
+
+ if ( (ret=sieve_header_list_next_item
+ (strlist->source, &hdr_name, str_r)) <= 0 ) {
+ if (ret < 0) {
+ _strlist->exec_status =
+ strlist->source->strlist.exec_status;
+ }
+ return ret;
+ }
+
+ *str_r = content_header_parse(strlist, hdr_name, *str_r);
+ return 1;
+}
+
+static void content_header_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct content_header_stringlist *strlist =
+ (struct content_header_stringlist *)_strlist;
+ sieve_header_list_reset(strlist->source);
+}
+
+static int content_header_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+ struct content_header_stringlist *strlist =
+ (struct content_header_stringlist *)_strlist;
+ return sieve_header_list_get_length(strlist->source);
+}
+
+static void content_header_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+ struct content_header_stringlist *strlist =
+ (struct content_header_stringlist *)_strlist;
+ sieve_header_list_set_trace(strlist->source, trace);
+}
+
+/*
+ * Header override implementation
+ */
+
+/* Context data */
+
+struct svmo_mime_context {
+ enum ext_mime_option mimeopt;
+ const char *const *params;
+ bool anychild:1;
+};
+
+/* Context coding */
+
+static bool svmo_mime_dump_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ unsigned int anychild, mimeopt;
+
+ if ( !sieve_binary_read_byte(denv->sblock, address, &anychild) )
+ return FALSE;
+ if ( anychild > 0 )
+ sieve_code_dumpf(denv, "anychild");
+
+ if ( !sieve_binary_read_byte(denv->sblock, address, &mimeopt) )
+ return FALSE;
+
+ switch ( mimeopt ) {
+ case EXT_MIME_OPTION_NONE:
+ break;
+ case EXT_MIME_OPTION_TYPE:
+ sieve_code_dumpf(denv, "option: type");
+ break;
+ case EXT_MIME_OPTION_SUBTYPE:
+ sieve_code_dumpf(denv, "option: subtype");
+ break;
+ case EXT_MIME_OPTION_CONTENTTYPE:
+ sieve_code_dumpf(denv, "option: contenttype");
+ break;
+ case EXT_MIME_OPTION_PARAM:
+ sieve_code_dumpf(denv, "option: param");
+ sieve_code_descend(denv);
+ if ( !sieve_opr_stringlist_dump(denv, address, "param-list") )
+ return FALSE;
+ sieve_code_ascend(denv);
+ break;
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int svmo_mime_read_context
+(const struct sieve_message_override *svmo ATTR_UNUSED,
+ const struct sieve_runtime_env *renv, sieve_size_t *address,
+ void **ho_context)
+{
+ pool_t pool = sieve_result_pool(renv->result); // FIXME: investigate
+ struct svmo_mime_context *ctx;
+ unsigned int anychild = 0, mimeopt = EXT_MIME_OPTION_NONE;
+ struct sieve_stringlist *param_list = NULL;
+ int ret;
+
+ if ( !sieve_binary_read_byte
+ (renv->sblock, address, &anychild) ) {
+ sieve_runtime_trace_error(renv,
+ "anychild: invalid byte");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( !sieve_binary_read_byte
+ (renv->sblock, address, &mimeopt) ||
+ mimeopt > EXT_MIME_OPTION_PARAM ) {
+ sieve_runtime_trace_error(renv,
+ "option: invalid mime option code");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( mimeopt == EXT_MIME_OPTION_PARAM &&
+ (ret=sieve_opr_stringlist_read
+ (renv, address, "param-list", &param_list)) <= 0 )
+ return ret;
+
+ ctx = p_new(pool, struct svmo_mime_context, 1);
+ ctx->anychild = (anychild == 0 ? FALSE : TRUE);
+ ctx->mimeopt = (enum ext_mime_option)mimeopt;
+
+ if ( param_list != NULL && sieve_stringlist_read_all
+ (param_list, pool, &ctx->params) < 0 ) {
+ sieve_runtime_trace_error(renv,
+ "failed to read param-list operand");
+ return param_list->exec_status;
+ }
+
+ *ho_context = (void *) ctx;
+ return SIEVE_EXEC_OK;
+}
+
+/* Override */
+
+static int svmo_mime_header_override
+(const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv, bool mime_decode,
+ struct sieve_stringlist **headers_r)
+{
+ struct svmo_mime_context *ctx =
+ (struct svmo_mime_context *)svmo->context;
+ struct ext_foreverypart_runtime_loop *sfploop;
+ struct sieve_header_list *headers;
+ struct sieve_stringlist *values;
+ int ret;
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "header mime override:");
+ sieve_runtime_trace_descend(renv);
+
+ if ( ctx->anychild ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "headers from current mime part and children");
+ } else {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "headers from current mime part");
+ }
+
+ sfploop = ext_foreverypart_runtime_loop_get_current(renv);
+ if ( sfploop != NULL ) {
+ headers = sieve_mime_header_list_create
+ (renv, *headers_r, &sfploop->part_iter,
+ mime_decode, ctx->anychild);
+ } else if ( ctx->anychild ) {
+ struct sieve_message_part_iter part_iter;
+
+ if ( (ret=sieve_message_part_iter_init
+ (&part_iter, renv)) <= 0 )
+ return ret;
+
+ headers = sieve_mime_header_list_create
+ (renv, *headers_r, &part_iter, mime_decode, TRUE);
+ } else {
+ headers = sieve_message_header_list_create
+ (renv, *headers_r, mime_decode);
+ }
+ values = &headers->strlist;
+
+ switch ( ctx->mimeopt ) {
+ case EXT_MIME_OPTION_NONE:
+ break;
+ case EXT_MIME_OPTION_TYPE:
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "extract mime type from header value");
+ break;
+ case EXT_MIME_OPTION_SUBTYPE:
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "extract mime subtype from header value");
+ break;
+ case EXT_MIME_OPTION_CONTENTTYPE:
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "extract mime contenttype from header value");
+ break;
+ case EXT_MIME_OPTION_PARAM:
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "extract mime parameters from header value");
+ break;
+ default:
+ i_unreached();
+ }
+
+ if ( ctx->mimeopt != EXT_MIME_OPTION_NONE ) {
+ values = content_header_stringlist_create
+ (renv, headers, ctx->mimeopt, ctx->params);
+ }
+ *headers_r = values;
+
+ sieve_runtime_trace_ascend(renv);
+ return SIEVE_EXEC_OK;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/Makefile.am b/pigeonhole/src/lib-sieve/plugins/notify/Makefile.am
new file mode 100644
index 0000000..aaa76b3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/Makefile.am
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libsieve_ext_notify.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-notify.c \
+ cmd-denotify.c
+
+libsieve_ext_notify_la_SOURCES = \
+ ext-notify.c \
+ ext-notify-common.c \
+ $(commands)
+
+noinst_HEADERS = \
+ ext-notify-common.h \
+ ext-notify-limits.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in b/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in
new file mode 100644
index 0000000..910007d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in
@@ -0,0 +1,699 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/notify
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_notify_la_LIBADD =
+am__objects_1 = cmd-notify.lo cmd-denotify.lo
+am_libsieve_ext_notify_la_OBJECTS = ext-notify.lo ext-notify-common.lo \
+ $(am__objects_1)
+libsieve_ext_notify_la_OBJECTS = $(am_libsieve_ext_notify_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-denotify.Plo \
+ ./$(DEPDIR)/cmd-notify.Plo ./$(DEPDIR)/ext-notify-common.Plo \
+ ./$(DEPDIR)/ext-notify.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_notify_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_notify_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_notify.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-notify.c \
+ cmd-denotify.c
+
+libsieve_ext_notify_la_SOURCES = \
+ ext-notify.c \
+ ext-notify-common.c \
+ $(commands)
+
+noinst_HEADERS = \
+ ext-notify-common.h \
+ ext-notify-limits.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/notify/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/notify/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_notify.la: $(libsieve_ext_notify_la_OBJECTS) $(libsieve_ext_notify_la_DEPENDENCIES) $(EXTRA_libsieve_ext_notify_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_notify_la_OBJECTS) $(libsieve_ext_notify_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-denotify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-notify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-notify-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-notify.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-denotify.Plo
+ -rm -f ./$(DEPDIR)/cmd-notify.Plo
+ -rm -f ./$(DEPDIR)/ext-notify-common.Plo
+ -rm -f ./$(DEPDIR)/ext-notify.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-denotify.Plo
+ -rm -f ./$(DEPDIR)/cmd-notify.Plo
+ -rm -f ./$(DEPDIR)/ext-notify-common.Plo
+ -rm -f ./$(DEPDIR)/ext-notify.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/cmd-denotify.c b/pigeonhole/src/lib-sieve/plugins/notify/cmd-denotify.c
new file mode 100644
index 0000000..8769808
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/cmd-denotify.c
@@ -0,0 +1,389 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-match-types.h"
+#include "sieve-comparators.h"
+#include "sieve-match.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+/*
+ * Denotify command
+ *
+ * Syntax:
+ * denotify [MATCH-TYPE string] [<":low" / ":normal" / ":high">]
+ */
+
+static bool cmd_denotify_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_denotify_pre_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_denotify_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_denotify_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_denotify = {
+ .identifier = "denotify",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_denotify_registered,
+ .pre_validate = cmd_denotify_pre_validate,
+ .validate = cmd_denotify_validate,
+ .generate = cmd_denotify_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool tag_match_type_is_instance_of
+ (struct sieve_validator *validator, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_match_type_validate
+ (struct sieve_validator *validator, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def denotify_match_tag = {
+ .identifier = "MATCH-TYPE-STRING",
+ .is_instance_of = tag_match_type_is_instance_of,
+ .validate = tag_match_type_validate
+};
+
+/* Codes for optional operands */
+
+enum cmd_denotify_optional {
+ OPT_END,
+ OPT_IMPORTANCE,
+ OPT_MATCH_TYPE,
+ OPT_MATCH_KEY
+};
+
+/*
+ * Denotify operation
+ */
+
+static bool cmd_denotify_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_denotify_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def denotify_operation = {
+ .mnemonic = "DENOTIFY",
+ .ext_def = &notify_extension,
+ .code = EXT_NOTIFY_OPERATION_DENOTIFY,
+ .dump = cmd_denotify_operation_dump,
+ .execute = cmd_denotify_operation_execute
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_denotify_context_data {
+ struct sieve_ast_argument *match_key_arg;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool tag_match_type_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data)
+{
+ return match_type_tag.is_instance_of(valdtr, cmd, ext, identifier, data);
+}
+
+static bool tag_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_denotify_context_data *cmd_data =
+ (struct cmd_denotify_context_data *) cmd->data;
+ struct sieve_ast_argument *tag = *arg;
+
+ i_assert(tag != NULL);
+
+ if ( !match_type_tag.validate(valdtr, arg, cmd) )
+ return FALSE;
+
+ if ( *arg == NULL ) {
+ sieve_argument_validate_error(valdtr, tag,
+ "the MATCH-TYPE argument (:%s) for the denotify command requires "
+ "an additional key-string parameter, but no more arguments were found",
+ sieve_ast_argument_tag(tag));
+ return FALSE;
+ }
+
+ if ( sieve_ast_argument_type(*arg) != SAAT_STRING )
+ {
+ sieve_argument_validate_error(valdtr, *arg,
+ "the MATCH-TYPE argument (:%s) for the denotify command requires "
+ "an additional key-string parameter, but %s was found",
+ sieve_ast_argument_tag(tag), sieve_ast_argument_name(*arg));
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, *arg, FALSE) )
+ return FALSE;
+
+ tag->argument->def = &match_type_tag;
+ tag->argument->ext = NULL;
+
+ (*arg)->argument->id_code = OPT_MATCH_KEY;
+ cmd_data->match_key_arg = *arg;
+
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_denotify_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &denotify_match_tag, OPT_MATCH_TYPE);
+
+ ext_notify_register_importance_tags(valdtr, cmd_reg, ext, OPT_IMPORTANCE);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_denotify_pre_validate
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct cmd_denotify_context_data *ctx_data;
+
+ /* Assign context */
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_denotify_context_data, 1);
+ cmd->data = (void *) ctx_data;
+
+ return TRUE;
+}
+
+static bool cmd_denotify_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct cmd_denotify_context_data *ctx_data =
+ (struct cmd_denotify_context_data *) cmd->data;
+ struct sieve_ast_argument *key_arg = ctx_data->match_key_arg;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+
+ if ( key_arg != NULL ) {
+ if ( !sieve_match_type_validate
+ (valdtr, cmd, key_arg, &mcht_default, &cmp_default) )
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_denotify_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &denotify_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_denotify_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ const struct sieve_operation *op = denv->oprtn;
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+ sieve_code_descend(denv);
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_MATCH_KEY:
+ opok = sieve_opr_string_dump(denv, address, "key-string");
+ break;
+ case OPT_MATCH_TYPE:
+ opok = sieve_opr_match_type_dump(denv, address);
+ break;
+ case OPT_IMPORTANCE:
+ opok = sieve_opr_number_dump(denv, address, "importance");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if ( !opok ) return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_denotify_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_stringlist *match_key = NULL;
+ sieve_number_t importance = 0;
+ struct sieve_match_context *mctx;
+ struct sieve_result_iterate_context *rictx;
+ const struct sieve_action *action;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_MATCH_TYPE:
+ ret = sieve_opr_match_type_read(renv, address, &mcht);
+ break;
+ case OPT_MATCH_KEY:
+ ret = sieve_opr_stringlist_read(renv, address, "match key", &match_key);
+ break;
+ case OPT_IMPORTANCE:
+ ret = sieve_opr_number_read(renv, address, "importance", &importance);
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( ret <= 0 ) return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ /* Enforce 0 < importance < 4 (just to be sure) */
+
+ if ( importance < 1 )
+ importance = 1;
+ else if ( importance > 3 )
+ importance = 3;
+
+ /* Trace */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "denotify action");
+
+ /* Either do string matching or just kill all notify actions */
+ if ( match_key != NULL ) {
+ /* Initialize match */
+ mctx = sieve_match_begin(renv, &mcht, &cmp);
+
+ /* Iterate through all notify actions and delete those that match */
+ rictx = sieve_result_iterate_init(renv->result);
+
+ while ( (action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+ if ( sieve_action_is(action, act_notify_old) ) {
+ struct ext_notify_action *nact =
+ (struct ext_notify_action *) action->context;
+
+ if ( importance == 0 || nact->importance == importance ) {
+ int match;
+
+ if ( (match=sieve_match_value
+ (mctx, nact->id, strlen(nact->id), match_key)) < 0 )
+ break;
+
+ if ( match > 0 )
+ sieve_result_iterate_delete(rictx);
+ }
+ }
+ }
+
+ /* Finish match */
+ if ( sieve_match_end(&mctx, &ret) < 0 )
+ return ret;
+
+ } else {
+ /* Delete all notify actions */
+ rictx = sieve_result_iterate_init(renv->result);
+
+ while ( (action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+
+ if ( sieve_action_is(action, act_notify_old) ) {
+ struct ext_notify_action *nact =
+ (struct ext_notify_action *) action->context;
+
+ if ( importance == 0 || nact->importance == importance )
+ sieve_result_iterate_delete(rictx);
+ }
+ }
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c b/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
new file mode 100644
index 0000000..a683c31
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
@@ -0,0 +1,900 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-notify-common.h"
+#include "ext-notify-limits.h"
+
+#include <ctype.h>
+
+/* Notify command (DEPRECATED)
+ *
+ * Syntax:
+ * notify [":method" string] [":id" string] [":options" string-list]
+ * [<":low" / ":normal" / ":high">] ["message:" string]
+ *
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_notify_pre_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_notify_old = {
+ .identifier = "notify",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_notify_registered,
+ .pre_validate = cmd_notify_pre_validate,
+ .validate = cmd_notify_validate,
+ .generate = cmd_notify_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def notify_method_tag = {
+ .identifier = "method",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_options_tag = {
+ .identifier = "options",
+ .validate = cmd_notify_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def notify_id_tag = {
+ .identifier = "id",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_message_tag = {
+ .identifier = "message",
+ .validate = cmd_notify_validate_string_tag
+};
+
+/*
+ * Notify operation
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def notify_old_operation = {
+ .mnemonic = "NOTIFY",
+ .ext_def = &notify_extension,
+ .code = EXT_NOTIFY_OPERATION_NOTIFY,
+ .dump = cmd_notify_operation_dump,
+ .execute = cmd_notify_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_notify_optional {
+ OPT_END,
+ OPT_MESSAGE,
+ OPT_IMPORTANCE,
+ OPT_OPTIONS,
+ OPT_ID
+};
+
+/*
+ * Notify action
+ */
+
+/* Forward declarations */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context);
+
+/* Action object */
+
+const struct sieve_action_def act_notify_old = {
+ .name = "notify",
+ .check_duplicate = act_notify_check_duplicate,
+ .print = act_notify_print,
+ .commit = act_notify_commit
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_notify_context_data {
+ struct sieve_ast_argument *id;
+ struct sieve_ast_argument *method;
+ struct sieve_ast_argument *options;
+ struct sieve_ast_argument *message;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Check syntax:
+ * :id <string>
+ * :method <string>
+ * :message <string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is(tag, notify_method_tag)) {
+ ctx_data->method = *arg;
+
+ /* Removed */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+ } else if (sieve_argument_is(tag, notify_id_tag)) {
+ ctx_data->id = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ } else if (sieve_argument_is(tag, notify_message_tag)) {
+ ctx_data->message = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ }
+ return TRUE;
+}
+
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :options string-list
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING_LIST, FALSE))
+ return FALSE;
+
+ /* Assign context */
+ ctx_data->options = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_method_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_id_tag, OPT_ID);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_message_tag, OPT_MESSAGE);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_options_tag, OPT_OPTIONS);
+
+ ext_notify_register_importance_tags(valdtr, cmd_reg, ext,
+ OPT_IMPORTANCE);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_notify_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct cmd_notify_context_data *ctx_data;
+
+ /* Create context */
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_notify_context_data, 1);
+ cmd->data = ctx_data;
+
+ return TRUE;
+}
+
+static int
+cmd_notify_address_validate(void *context, struct sieve_ast_argument *arg)
+{
+ struct sieve_validator *valdtr = (struct sieve_validator *)context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *address = sieve_ast_argument_str(arg);
+ const char *error;
+ int result;
+
+ T_BEGIN {
+ result = (sieve_address_validate_str(address, &error) ?
+ 1 : -1);
+
+ if (result <= 0) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "specified :options address '%s' is invalid for "
+ "the mailto notify method: %s",
+ str_sanitize(str_c(address), 128),
+ error);
+ }
+ } T_END;
+
+ return result;
+ }
+
+ return 1;
+}
+
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Check :method argument */
+ if (ctx_data->method != NULL) {
+ const char *method = sieve_ast_argument_strc(ctx_data->method);
+
+ if (strcasecmp(method, "mailto") != 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the notify command of the deprecated notify extension "
+ "only supports the 'mailto' notification method");
+ return FALSE;
+ }
+ }
+
+ /* Check :options argument */
+ if (ctx_data->options != NULL) {
+ struct sieve_ast_argument *option = ctx_data->options;
+
+ /* Parse and check options */
+ if (sieve_ast_stringlist_map(
+ &option, (void *)valdtr,
+ cmd_notify_address_validate) <= 0) {
+ return FALSE;
+ }
+ } else {
+ sieve_command_validate_warning(
+ valdtr, cmd,
+ "no :options (and hence recipients) specified for the notify command");
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &notify_old_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "NOTIFY");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_IMPORTANCE:
+ opok = sieve_opr_number_dump(denv, address,
+ "importance");
+ break;
+ case OPT_ID:
+ opok = sieve_opr_string_dump(denv, address,
+ "id");
+ break;
+ case OPT_OPTIONS:
+ opok = sieve_opr_stringlist_dump(denv, address,
+ "options");
+ break;
+ case OPT_MESSAGE:
+ opok = sieve_opr_string_dump(denv, address,
+ "message");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_notify_action *act;
+ pool_t pool;
+ int opt_code = 0;
+ sieve_number_t importance = 1;
+ struct sieve_stringlist *options = NULL;
+ string_t *message = NULL, *id = NULL;
+ int ret = 0;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_IMPORTANCE:
+ ret = sieve_opr_number_read(renv, address, "importance",
+ &importance);
+ break;
+ case OPT_ID:
+ ret = sieve_opr_string_read(renv, address, "id", &id);
+ break;
+ case OPT_MESSAGE:
+ ret = sieve_opr_string_read(renv, address, "from",
+ &message);
+ break;
+ case OPT_OPTIONS:
+ ret = sieve_opr_stringlist_read(renv, address,
+ "options", &options);
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0) return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ /* Enforce 0 < importance < 4 (just to be sure) */
+
+ if (importance < 1)
+ importance = 1;
+ else if (importance > 3)
+ importance = 3;
+
+ /* Trace */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "notify action");
+
+ /* Compose action */
+ if (options != NULL) {
+ string_t *raw_address;
+ string_t *out_message;
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct ext_notify_action, 1);
+ if (id != NULL)
+ act->id = p_strdup(pool, str_c(id));
+ act->importance = importance;
+
+ /* Process message */
+
+ out_message = t_str_new(1024);
+ if ((ret = ext_notify_construct_message(
+ renv, (message == NULL ? NULL : str_c(message)),
+ out_message)) <= 0)
+ return ret;
+ act->message = p_strdup(pool, str_c(out_message));
+
+ /* Normalize and verify all :options addresses */
+
+ sieve_stringlist_reset(options);
+
+ p_array_init(&act->recipients, pool, 4);
+
+ raw_address = NULL;
+ while ((ret = sieve_stringlist_next_item(
+ options, &raw_address)) > 0) {
+ const char *error = NULL;
+ const struct smtp_address *address;
+
+ /* Add if valid address */
+ address = sieve_address_parse_str(raw_address, &error);
+ if (address != NULL) {
+ const struct ext_notify_recipient *rcpts;
+ unsigned int rcpt_count, i;
+
+ /* Prevent duplicates */
+ rcpts = array_get(&act->recipients, &rcpt_count);
+
+ for (i = 0; i < rcpt_count; i++) {
+ if (smtp_address_equals(rcpts[i].address,
+ address))
+ break;
+ }
+
+ /* Add only if unique */
+ if (i != rcpt_count) {
+ sieve_runtime_warning(
+ renv, NULL,
+ "duplicate recipient '%s' specified in the :options argument of "
+ "the deprecated notify command",
+ str_sanitize(str_c(raw_address), 128));
+
+ } else if (array_count(&act->recipients) >=
+ EXT_NOTIFY_MAX_RECIPIENTS) {
+ sieve_runtime_warning(renv, NULL,
+ "more than the maximum %u recipients are specified "
+ "for the deprecated notify command; "
+ "the rest is discarded",
+ EXT_NOTIFY_MAX_RECIPIENTS);
+ break;
+
+ } else {
+ struct ext_notify_recipient recipient;
+
+ recipient.full =
+ p_strdup(pool, str_c(raw_address));
+ recipient.address =
+ smtp_address_clone(pool, address);
+
+ array_append(&act->recipients, &recipient, 1);
+ }
+ } else {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified :options address '%s' is invalid for "
+ "the deprecated notify command: %s",
+ str_sanitize(str_c(raw_address), 128), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+ }
+
+ if (ret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid options stringlist");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (sieve_result_add_action(renv, this_ext, "notify",
+ &act_notify_old, NULL, (void *)act,
+ 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *act ATTR_UNUSED,
+ const struct sieve_action *act_other ATTR_UNUSED)
+{
+ struct ext_notify_action *new_nact, *old_nact;
+ const struct ext_notify_recipient *new_rcpts;
+ const struct ext_notify_recipient *old_rcpts;
+ unsigned int new_count, old_count, i, j;
+ unsigned int del_start = 0, del_len = 0;
+
+ if (act->context == NULL || act_other->context == NULL)
+ return 0;
+
+ new_nact = (struct ext_notify_action *)act->context;
+ old_nact = (struct ext_notify_action *)act_other->context;
+
+ new_rcpts = array_get(&new_nact->recipients, &new_count);
+ old_rcpts = array_get(&old_nact->recipients, &old_count);
+
+ for (i = 0; i < new_count; i++) {
+ for (j = 0; j < old_count; j++) {
+ if (smtp_address_equals(new_rcpts[i].address,
+ old_rcpts[j].address))
+ break;
+ }
+
+ if (j == old_count) {
+ /* Not duplicate */
+ if (del_len > 0) {
+ /* Perform pending deletion */
+ array_delete(&new_nact->recipients,
+ del_start, del_len);
+
+ /* Make sure the loop integrity is maintained */
+ i -= del_len;
+ new_rcpts = array_get(&new_nact->recipients,
+ &new_count);
+ }
+
+ del_len = 0;
+ } else {
+ /* Mark deletion */
+ if (del_len == 0)
+ del_start = i;
+ del_len++;
+ }
+ }
+
+ /* Perform pending deletion */
+ if (del_len > 0)
+ array_delete(&new_nact->recipients, del_start, del_len);
+
+ return (array_count(&new_nact->recipients) > 0 ? 0 : 1);
+}
+
+/* Result printing */
+
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ const struct ext_notify_action *act =
+ (const struct ext_notify_action *)action->context;
+ const struct ext_notify_recipient *recipients;
+ unsigned int count, i;
+
+ sieve_result_action_printf(
+ rpenv, "send (deprecated) notification with method 'mailto':");
+
+ /* Print main method parameters */
+
+ sieve_result_printf(rpenv, " => importance : %llu\n",
+ (unsigned long long)act->importance);
+
+ if (act->message != NULL) {
+ sieve_result_printf(
+ rpenv, " => message : %s\n", act->message);
+ }
+ if (act->id != NULL) {
+ sieve_result_printf(
+ rpenv, " => id : %s \n", act->id);
+ }
+
+ /* Print mailto: recipients */
+
+ sieve_result_printf(rpenv, " => recipients :\n");
+
+ recipients = array_get(&act->recipients, &count);
+ if (count == 0) {
+ sieve_result_printf(
+ rpenv, " NONE, action has no effect\n");
+ } else {
+ for (i = 0; i < count; i++) {
+ sieve_result_printf(
+ rpenv, " + To: %s\n", recipients[i].full);
+ }
+ }
+
+ /* Finish output with an empty line */
+
+ sieve_result_printf(rpenv, "\n");
+}
+
+/* Result execution */
+
+static bool contains_8bit(const char *msg)
+{
+ const unsigned char *s = (const unsigned char *)msg;
+
+ for (; *s != '\0'; s++) {
+ if ((*s & 0x80) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+act_notify_send(const struct sieve_action_exec_env *aenv,
+ const struct ext_notify_action *act)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct ext_notify_recipient *recipients;
+ struct sieve_smtp_context *sctx;
+ unsigned int count, i;
+ struct ostream *output;
+ string_t *msg, *to, *all;
+ const char *outmsgid, *error;
+ int ret;
+
+ /* Get recipients */
+ recipients = array_get(&act->recipients, &count);
+ if (count == 0) {
+ sieve_result_warning(
+ aenv, "notify action specifies no recipients; "
+ "action has no effect");
+ return TRUE;
+ }
+
+ /* Just to be sure */
+ if (!sieve_smtp_available(senv)) {
+ sieve_result_global_warning(
+ aenv, "notify action has no means to send mail");
+ return TRUE;
+ }
+
+ /* Compose common headers */
+ msg = t_str_new(512);
+ rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+ /* Set importance */
+ switch (act->importance) {
+ case 1:
+ rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
+ rfc2822_header_write(msg, "Importance", "High");
+ break;
+ case 3:
+ rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
+ rfc2822_header_write(msg, "Importance", "Low");
+ break;
+ case 2:
+ default:
+ rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
+ rfc2822_header_write(msg, "Importance", "Normal");
+ break;
+ }
+
+ rfc2822_header_write(msg, "From", sieve_get_postmaster_address(senv));
+
+ rfc2822_header_write(msg, "Subject", "[SIEVE] New mail notification");
+
+ rfc2822_header_write(msg, "Auto-Submitted", "auto-generated (notify)");
+ rfc2822_header_write(msg, "Precedence", "bulk");
+
+ rfc2822_header_write(msg, "MIME-Version", "1.0");
+ if (contains_8bit(act->message)) {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=utf-8");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+ } else {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=us-ascii");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+ }
+
+ outmsgid = sieve_message_get_new_id(eenv->svinst);
+ rfc2822_header_write(msg, "Message-ID", outmsgid);
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 &&
+ sieve_message_get_sender(aenv->msgctx) != NULL) {
+ sctx = sieve_smtp_start(senv, sieve_get_postmaster_smtp(senv));
+ } else {
+ sctx = sieve_smtp_start(senv, NULL);
+ }
+
+ /* Add all recipients (and compose To header field) */
+ to = t_str_new(128);
+ all = t_str_new(256);
+ for (i = 0; i < count; i++) {
+ sieve_smtp_add_rcpt(sctx, recipients[i].address);
+ if (i > 0)
+ str_append(to, ", ");
+ str_append(to, recipients[i].full);
+ if (i < 3) {
+ if (i > 0)
+ str_append(all, ", ");
+ str_append(all, smtp_address_encode_path(
+ recipients[i].address));
+ } else if (i == 3) {
+ str_printfa(all, ", ... (%u total)", count);
+ }
+ }
+
+ rfc2822_header_write_address(msg, "To", str_c(to));
+
+ /* Generate message body */
+ str_printfa(msg, "\r\n%s\r\n", act->message);
+
+ output = sieve_smtp_send(sctx);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(
+ aenv, "failed to send mail notification to %s: "
+ "%s (temporary failure)", str_c(all),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(
+ aenv, "failed to send mail notification to %s: "
+ "%s (permanent failure)", str_c(all),
+ str_sanitize(error, 512));
+ }
+ } else {
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("notify_target", str_c(all));
+
+ sieve_result_event_log(aenv, e->event(),
+ "sent mail notification to %s",
+ str_c(all));
+ }
+
+ return TRUE;
+}
+
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct mail *mail = eenv->msgdata->mail;
+ const struct ext_notify_action *act =
+ (const struct ext_notify_action *)aenv->action->context;
+ const char *const *hdsp;
+ bool result;
+ int ret;
+
+ /* Is the message an automatic reply ? */
+ if ((ret = mail_get_headers(mail, "auto-submitted", &hdsp)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read `auto-submitted' header field");
+ }
+
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if (ret > 0) {
+ while (*hdsp != NULL) {
+ if (strcasecmp(*hdsp, "no") != 0) {
+ const struct smtp_address *sender = NULL;
+ const char *from;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
+ sender = sieve_message_get_sender(aenv->msgctx);
+ from = (sender == NULL ? "" :
+ t_strdup_printf(" from <%s>",
+ smtp_address_encode(sender)));
+
+ sieve_result_global_log(
+ aenv,
+ "not sending notification for auto-submitted message%s",
+ from);
+ return SIEVE_EXEC_OK;
+ }
+ hdsp++;
+ }
+ }
+
+ T_BEGIN {
+ result = act_notify_send(aenv, act);
+ } T_END;
+
+ if (!result)
+ return SIEVE_EXEC_FAILURE;
+ eenv->exec_status->significant_action_executed = TRUE;
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.c b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.c
new file mode 100644
index 0000000..589b21b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.c
@@ -0,0 +1,361 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "rfc822-parser.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+#include <ctype.h>
+
+/*
+ * Importance argument
+ */
+
+static bool
+tag_importance_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def importance_low_tag = {
+ .identifier = "low",
+ .validate = tag_importance_validate,
+};
+
+static const struct sieve_argument_def importance_normal_tag = {
+ .identifier = "normal",
+ .validate = tag_importance_validate,
+};
+
+static const struct sieve_argument_def importance_high_tag = {
+ .identifier = "high",
+ .validate = tag_importance_validate,
+};
+
+static bool
+tag_importance_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ if (sieve_argument_is(tag, importance_low_tag))
+ sieve_ast_argument_number_substitute(tag, 3);
+ else if (sieve_argument_is(tag, importance_normal_tag))
+ sieve_ast_argument_number_substitute(tag, 2);
+ else
+ sieve_ast_argument_number_substitute(tag, 1);
+
+ tag->argument = sieve_argument_create(tag->ast, &number_argument,
+ tag->argument->ext,
+ tag->argument->id_code);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+void ext_notify_register_importance_tags(
+ struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext, unsigned int id_code)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &importance_low_tag, id_code);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &importance_normal_tag, id_code);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &importance_high_tag, id_code);
+}
+
+/*
+ * Body extraction
+ */
+
+/* FIXME: overlaps somewhat with body extension */
+
+struct ext_notify_message_context {
+ pool_t pool;
+ buffer_t *body_text;
+};
+
+static struct ext_notify_message_context *
+ext_notify_get_message_context(const struct sieve_extension *this_ext,
+ struct sieve_message_context *msgctx)
+{
+ struct ext_notify_message_context *ctx;
+
+ /* Get message context (contains cached message body information) */
+ ctx = (struct ext_notify_message_context *)
+ sieve_message_context_extension_get(msgctx, this_ext);
+
+ /* Create it if it does not exist already */
+ if (ctx == NULL) {
+ pool_t pool = sieve_message_context_pool(msgctx);
+ ctx = p_new(pool, struct ext_notify_message_context, 1);
+ ctx->pool = pool;
+ ctx->body_text = NULL;
+
+ /* Register context */
+ sieve_message_context_extension_set(msgctx, this_ext,
+ (void *)ctx);
+ }
+
+ return ctx;
+}
+
+static bool _is_text_content(const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_type;
+ const char *data;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type */
+ content_type = t_str_new(64);
+ if (rfc822_parse_content_type(&parser, content_type) < 0)
+ return FALSE;
+
+ /* Content-type value must end here, otherwise it is invalid after all
+ */
+ (void)rfc822_skip_lwsp(&parser);
+ if (parser.data != parser.end && *parser.data != ';')
+ return FALSE;
+
+ /* Success */
+ data = str_c(content_type);
+ if (str_begins(data, "text/"))
+ return TRUE;
+ return FALSE;
+}
+
+static int
+cmd_notify_extract_body_text(const struct sieve_runtime_env *renv,
+ const char **body_text_r, size_t *body_size_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_notify_message_context *mctx;
+ struct mail *mail = eenv->msgdata->mail;
+ struct message_parser_ctx *parser;
+ struct message_decoder_context *decoder;
+ struct message_part *parts;
+ struct message_block block, decoded;
+ struct istream *input;
+ bool is_text, save_body;
+ int ret = 1;
+
+ *body_text_r = NULL;
+ *body_size_r = 0;
+
+ /* Return cached result if available */
+ mctx = ext_notify_get_message_context(this_ext, renv->msgctx);
+ if (mctx->body_text != NULL) {
+ *body_text_r = (const char *)
+ buffer_get_data(mctx->body_text, body_size_r);
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Create buffer */
+ mctx->body_text = buffer_create_dynamic(mctx->pool, 1024*64);
+
+ /* Get the message stream */
+ if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
+ return sieve_runtime_mail_error(renv, mail, "notify action: "
+ "failed to read input message");
+ }
+
+ /* Initialize body decoder */
+ decoder = message_decoder_init(NULL, 0);
+
+ struct message_parser_settings mparser_set = {
+ .hdr_flags = 0,
+ .flags = 0,
+ };
+ parser = message_parser_init(mctx->pool, input, &mparser_set);
+ is_text = TRUE;
+ save_body = FALSE;
+ while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
+ if (block.hdr != NULL || block.size == 0) {
+ /* Decode block */
+ (void)message_decoder_decode_next_block(decoder, &block,
+ &decoded);
+
+ /* Check for end of headers */
+ if (block.hdr == NULL) {
+ save_body = is_text;
+ continue;
+ }
+
+ /* We're interested of only Content-Type: header */
+ if (strcasecmp(block.hdr->name, "Content-Type") != 0)
+ continue;
+
+ /* Header can have folding whitespace. Acquire the full
+ value before continuing */
+ if (block.hdr->continues) {
+ block.hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ /* Is it a text part? */
+ T_BEGIN {
+ is_text = _is_text_content(block.hdr);
+ } T_END;
+
+ continue;
+ }
+
+ /* Read text body */
+ if (save_body) {
+ (void)message_decoder_decode_next_block(decoder, &block,
+ &decoded);
+ buffer_append(mctx->body_text, decoded.data,
+ decoded.size);
+ is_text = TRUE;
+ }
+ }
+
+ /* Cleanup */
+ (void)message_parser_deinit(&parser, &parts);
+ message_decoder_deinit(&decoder);
+
+ if (ret < 0 && input->stream_errno != 0) {
+ sieve_runtime_critical(renv, NULL, "notify action: "
+ "failed to read input message",
+ "notify action: read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+
+ /* Return status */
+ *body_text_r = (const char *)buffer_get_data(mctx->body_text,
+ body_size_r);
+ return SIEVE_EXEC_OK;
+}
+
+int ext_notify_construct_message(const struct sieve_runtime_env *renv,
+ const char *msg_format,
+ string_t *out_msg)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ const struct smtp_address *return_path =
+ sieve_message_get_sender(msgctx);
+ const char *p;
+ int ret;
+
+ if (msg_format == NULL)
+ msg_format = "$from$: $subject$";
+
+ /* Scan message for substitutions */
+ p = msg_format;
+ while (*p != '\0') {
+ const char *header;
+
+ if (strncasecmp(p, "$from$", 6) == 0) {
+ p += 6;
+
+ /* Fetch sender from original message */
+ if ((ret = mail_get_first_header_utf8(
+ msgdata->mail, "from", &header)) < 0) {
+ return sieve_runtime_mail_error(
+ renv, msgdata->mail,
+ "failed to read header field `from'");
+ }
+ if (ret > 0)
+ str_append(out_msg, header);
+ } else if (strncasecmp(p, "$env-from$", 10) == 0) {
+ p += 10;
+
+ if (return_path != NULL)
+ smtp_address_write(out_msg, return_path);
+ } else if (strncasecmp(p, "$subject$", 9) == 0) {
+ p += 9;
+
+ /* Fetch sender from oriinal message */
+ if ((ret = mail_get_first_header_utf8(
+ msgdata->mail, "subject", &header)) < 0) {
+ return sieve_runtime_mail_error(
+ renv, msgdata->mail,
+ "failed to read header field `subject'");
+ }
+ if (ret > 0)
+ str_append(out_msg, header);
+ } else if (strncasecmp(p, "$text", 5) == 0 &&
+ (p[5] == '[' || p[5] == '$')) {
+ size_t num = 0;
+ const char *begin = p;
+ bool valid = TRUE;
+
+ p += 5;
+ if (*p == '[') {
+ p += 1;
+
+ while (i_isdigit(*p)) {
+ num = num * 10 + (*p - '0');
+ p++;
+ }
+
+ if (*p++ != ']' || *p++ != '$') {
+ str_append_data(out_msg, begin,
+ p-begin);
+ valid = FALSE;
+ }
+ } else {
+ p += 1;
+ }
+
+ if (valid) {
+ size_t body_size;
+ const char *body_text;
+
+ if ((ret = cmd_notify_extract_body_text(
+ renv, &body_text, &body_size)) <= 0)
+ return ret;
+
+ if (num > 0 && num < body_size) {
+ str_append_data(out_msg, body_text,
+ num);
+ } else {
+ str_append_data(out_msg, body_text,
+ body_size);
+ }
+ }
+ } else {
+ size_t len;
+
+ /* Find next substitution */
+ len = strcspn(p + 1, "$") + 1;
+
+ /* Copy normal text */
+ str_append_data(out_msg, p, len);
+ p += len;
+ }
+ }
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.h b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.h
new file mode 100644
index 0000000..09db2cb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-common.h
@@ -0,0 +1,66 @@
+#ifndef EXT_NOTIFY_COMMON_H
+#define EXT_NOTIFY_COMMON_H
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def notify_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_notify_old;
+extern const struct sieve_command_def cmd_denotify;
+
+/*
+ * Arguments
+ */
+
+void ext_notify_register_importance_tags(
+ struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *this_ext, unsigned int id_code);
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def notify_old_operation;
+extern const struct sieve_operation_def denotify_operation;
+
+enum ext_notify_opcode {
+ EXT_NOTIFY_OPERATION_NOTIFY,
+ EXT_NOTIFY_OPERATION_DENOTIFY,
+};
+
+/*
+ * Actions
+ */
+
+extern const struct sieve_action_def act_notify_old;
+
+struct ext_notify_recipient {
+ const char *full;
+ const struct smtp_address *address;
+};
+
+ARRAY_DEFINE_TYPE(recipients, struct ext_notify_recipient);
+
+struct ext_notify_action {
+ const char *id;
+ const char *message;
+ sieve_number_t importance;
+
+ ARRAY_TYPE(recipients) recipients;
+};
+
+/*
+ * Message construct
+ */
+
+int ext_notify_construct_message(const struct sieve_runtime_env *renv,
+ const char *msg_format, string_t *out_msg);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-limits.h b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-limits.h
new file mode 100644
index 0000000..5fab7cb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify-limits.h
@@ -0,0 +1,7 @@
+#ifndef EXT_NOTIFY_LIMITS_H
+#define EXT_NOTIFY_LIMITS_H
+
+#define EXT_NOTIFY_MAX_RECIPIENTS 8
+#define EXT_NOTIFY_MAX_MESSAGE 256
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/ext-notify.c b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify.c
new file mode 100644
index 0000000..e79e049
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/ext-notify.c
@@ -0,0 +1,108 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension notify
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-ietf-sieve-notify-00.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-notify-common.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_notify_operations[] = {
+ &notify_old_operation,
+ &denotify_operation
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_notify_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def notify_extension = {
+ .name = "notify",
+ .validator_load = ext_notify_validator_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_notify_operations)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_notify_validator_check_conflict
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required);
+static bool ext_notify_validator_validate
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+
+const struct sieve_validator_extension notify_validator_extension = {
+ .ext = &notify_extension,
+ .check_conflict = ext_notify_validator_check_conflict,
+ .validate = ext_notify_validator_validate
+};
+
+static bool ext_notify_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register validator extension to check for conflict with enotify */
+ sieve_validator_extension_register
+ (valdtr, ext, &notify_validator_extension, NULL);
+ return TRUE;
+}
+
+static bool ext_notify_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required ATTR_UNUSED)
+{
+ /* Check for conflict with enotify */
+ if ( sieve_extension_name_is(ext_other, "enotify") ) {
+ sieve_argument_validate_error(valdtr, require_arg,
+ "the (deprecated) notify extension cannot be used "
+ "together with the enotify extension");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool ext_notify_validator_validate
+(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg ATTR_UNUSED,
+ bool required ATTR_UNUSED)
+{
+ /* No conflicts: register new commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_notify_old);
+ sieve_validator_register_command(valdtr, ext, &cmd_denotify);
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/Makefile.am b/pigeonhole/src/lib-sieve/plugins/regex/Makefile.am
new file mode 100644
index 0000000..eeb735e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_ext_regex.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_regex_la_SOURCES = \
+ mcht-regex.c \
+ ext-regex-common.c \
+ ext-regex.c
+
+noinst_HEADERS = \
+ ext-regex-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in b/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in
new file mode 100644
index 0000000..379d384
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in
@@ -0,0 +1,688 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/regex
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_regex_la_LIBADD =
+am_libsieve_ext_regex_la_OBJECTS = mcht-regex.lo ext-regex-common.lo \
+ ext-regex.lo
+libsieve_ext_regex_la_OBJECTS = $(am_libsieve_ext_regex_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-regex-common.Plo \
+ ./$(DEPDIR)/ext-regex.Plo ./$(DEPDIR)/mcht-regex.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_regex_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_regex_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_regex.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_regex_la_SOURCES = \
+ mcht-regex.c \
+ ext-regex-common.c \
+ ext-regex.c
+
+noinst_HEADERS = \
+ ext-regex-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/regex/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/regex/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_regex.la: $(libsieve_ext_regex_la_OBJECTS) $(libsieve_ext_regex_la_DEPENDENCIES) $(EXTRA_libsieve_ext_regex_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_regex_la_OBJECTS) $(libsieve_ext_regex_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-regex-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-regex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-regex.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-regex-common.Plo
+ -rm -f ./$(DEPDIR)/ext-regex.Plo
+ -rm -f ./$(DEPDIR)/mcht-regex.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-regex-common.Plo
+ -rm -f ./$(DEPDIR)/ext-regex.Plo
+ -rm -f ./$(DEPDIR)/mcht-regex.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.c b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.c
new file mode 100644
index 0000000..975f4fb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.c
@@ -0,0 +1,22 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-match-types.h"
+
+#include "ext-regex-common.h"
+
+/*
+ * Regex match type operand
+ */
+
+static const struct sieve_extension_objects ext_match_types =
+ SIEVE_EXT_DEFINE_MATCH_TYPE(regex_match_type);
+
+const struct sieve_operand_def regex_match_type_operand = {
+ .name = "regex match",
+ .ext_def = &regex_extension,
+ .class = &sieve_match_type_operand_class,
+ .interface = &ext_match_types
+};
+
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.h b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.h
new file mode 100644
index 0000000..e9d809f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex-common.h
@@ -0,0 +1,24 @@
+#ifndef EXT_REGEX_COMMON_H
+#define EXT_REGEX_COMMON_H
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def regex_extension;
+
+/*
+ * Operand
+ */
+
+extern const struct sieve_operand_def regex_match_type_operand;
+
+/*
+ * Match type
+ */
+
+extern const struct sieve_match_type_def regex_match_type;
+
+#endif
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/ext-regex.c b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex.c
new file mode 100644
index 0000000..0440b30
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/ext-regex.c
@@ -0,0 +1,65 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension regex
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-murchison-sieve-regex-08 (not latest)
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* FIXME: Regular expressions are compiled during compilation and
+ * again during interpretation. This is suboptimal and should be
+ * changed. This requires dumping the compiled regex to the binary.
+ * Most likely, this will only be possible when we implement regular
+ * expressions ourselves.
+ *
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-regex-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+/*
+ * Extension
+ */
+
+static bool ext_regex_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def regex_extension = {
+ .name = "regex",
+ .validator_load = ext_regex_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(regex_match_type_operand)
+};
+
+static bool ext_regex_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_match_type_register(valdtr, ext, &regex_match_type);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c b/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
new file mode 100644
index 0000000..f0c630e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
@@ -0,0 +1,385 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':regex'
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-interpreter.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-match.h"
+
+#include "ext-regex-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+#include <ctype.h>
+
+/*
+ * Configuration
+ */
+
+#define MCHT_REGEX_MAX_SUBSTITUTIONS SIEVE_MAX_MATCH_VALUES
+
+/*
+ * Match type
+ */
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
+
+static void mcht_regex_match_init(struct sieve_match_context *mctx);
+static int mcht_regex_match_keys
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ struct sieve_stringlist *key_list);
+static void mcht_regex_match_deinit(struct sieve_match_context *mctx);
+
+const struct sieve_match_type_def regex_match_type = {
+ SIEVE_OBJECT("regex", &regex_match_type_operand, 0),
+ .validate_context = mcht_regex_validate_context,
+ .match_init = mcht_regex_match_init,
+ .match_keys = mcht_regex_match_keys,
+ .match_deinit = mcht_regex_match_deinit
+};
+
+/*
+ * Match type validation
+ */
+
+/* Wrapper around the regerror function for easy access */
+static const char *_regexp_error(regex_t *regexp, int errorcode)
+{
+ size_t errsize = regerror(errorcode, regexp, NULL, 0);
+
+ if ( errsize > 0 ) {
+ char *errbuf;
+
+ buffer_t *error_buf =
+ buffer_create_dynamic(pool_datastack_create(), errsize);
+ errbuf = buffer_get_space_unsafe(error_buf, 0, errsize);
+
+ errsize = regerror(errorcode, regexp, errbuf, errsize);
+
+ /* We don't want the error to start with a capital letter */
+ errbuf[0] = i_tolower(errbuf[0]);
+
+ buffer_append_space_unsafe(error_buf, errsize);
+
+ return str_c(error_buf);
+ }
+
+ return "";
+}
+
+static int mcht_regex_validate_regexp
+(struct sieve_validator *valdtr,
+ struct sieve_match_type_context *mtctx ATTR_UNUSED,
+ struct sieve_ast_argument *key, int cflags)
+{
+ int ret;
+ regex_t regexp;
+ const char *regex_str = sieve_ast_argument_strc(key);
+
+ if ( (ret=regcomp(&regexp, regex_str, cflags)) != 0 ) {
+ sieve_argument_validate_error(valdtr, key,
+ "invalid regular expression '%s' for regex match: %s",
+ str_sanitize(regex_str, 128), _regexp_error(&regexp, ret));
+
+ regfree(&regexp);
+ return -1;
+ }
+
+ regfree(&regexp);
+ return 1;
+}
+
+struct _regex_key_context {
+ struct sieve_validator *valdtr;
+ struct sieve_match_type_context *mtctx;
+ int cflags;
+};
+
+static int mcht_regex_validate_key_argument
+(void *context, struct sieve_ast_argument *key)
+{
+ struct _regex_key_context *keyctx = (struct _regex_key_context *) context;
+
+ /* FIXME: We can currently only handle string literal argument, so
+ * variables are not allowed.
+ */
+ if ( sieve_argument_is_string_literal(key) ) {
+ return mcht_regex_validate_regexp
+ (keyctx->valdtr, keyctx->mtctx, key, keyctx->cflags);
+ }
+
+ return 1;
+}
+
+static bool mcht_regex_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg ATTR_UNUSED,
+ struct sieve_match_type_context *mtctx, struct sieve_ast_argument *key_arg)
+{
+ const struct sieve_comparator *cmp = mtctx->comparator;
+ int cflags = REG_EXTENDED | REG_NOSUB;
+ struct _regex_key_context keyctx;
+ struct sieve_ast_argument *kitem;
+
+ if ( cmp != NULL ) {
+ if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+ cflags = REG_EXTENDED | REG_NOSUB | REG_ICASE;
+ else if ( sieve_comparator_is(cmp, i_octet_comparator) )
+ cflags = REG_EXTENDED | REG_NOSUB;
+ else {
+ sieve_argument_validate_error(valdtr, mtctx->argument,
+ "regex match type only supports "
+ "i;octet and i;ascii-casemap comparators" );
+ return FALSE;
+ }
+ }
+
+ /* Validate regular expression keys */
+
+ keyctx.valdtr = valdtr;
+ keyctx.mtctx = mtctx;
+ keyctx.cflags = cflags;
+
+ kitem = key_arg;
+ if ( sieve_ast_stringlist_map(&kitem, (void *) &keyctx,
+ mcht_regex_validate_key_argument) <= 0 )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Match type implementation
+ */
+
+struct mcht_regex_key {
+ regex_t regexp;
+ int status;
+};
+
+struct mcht_regex_context {
+ ARRAY(struct mcht_regex_key) reg_expressions;
+ regmatch_t *pmatch;
+ size_t nmatch;
+ bool all_compiled:1;
+};
+
+static void mcht_regex_match_init
+(struct sieve_match_context *mctx)
+{
+ pool_t pool = mctx->pool;
+ struct mcht_regex_context *ctx;
+
+ /* Create context */
+ ctx = p_new(pool, struct mcht_regex_context, 1);
+
+ /* Create storage for match values if match values are requested */
+ if ( sieve_match_values_are_enabled(mctx->runenv) ) {
+ ctx->pmatch = p_new(pool, regmatch_t, MCHT_REGEX_MAX_SUBSTITUTIONS);
+ ctx->nmatch = MCHT_REGEX_MAX_SUBSTITUTIONS;
+ } else {
+ ctx->pmatch = NULL;
+ ctx->nmatch = 0;
+ }
+
+ /* Assign context */
+ mctx->data = (void *) ctx;
+}
+
+static int mcht_regex_match_key
+(struct sieve_match_context *mctx, const char *val,
+ const regex_t *regexp)
+{
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ int ret;
+
+ /* Execute regex */
+
+ ret = regexec(regexp, val, ctx->nmatch, ctx->pmatch, 0);
+
+ /* Handle match values if necessary */
+
+ if ( ret == 0 ) {
+ if ( ctx->nmatch > 0 ) {
+ struct sieve_match_values *mvalues;
+ size_t i;
+ int skipped = 0;
+ string_t *subst = t_str_new(32);
+
+ /* Start new list of match values */
+ mvalues = sieve_match_values_start(mctx->runenv);
+
+ i_assert( mvalues != NULL );
+
+ /* Add match values from regular expression */
+ for ( i = 0; i < ctx->nmatch; i++ ) {
+ str_truncate(subst, 0);
+
+ if ( ctx->pmatch[i].rm_so != -1 ) {
+ if ( skipped > 0 ) {
+ sieve_match_values_skip(mvalues, skipped);
+ skipped = 0;
+ }
+
+ str_append_data(subst, val + ctx->pmatch[i].rm_so,
+ ctx->pmatch[i].rm_eo - ctx->pmatch[i].rm_so);
+ sieve_match_values_add(mvalues, subst);
+ } else
+ skipped++;
+ }
+
+ /* Substitute the new match values */
+ sieve_match_values_commit(mctx->runenv, &mvalues);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static int mcht_regex_match_keys
+(struct sieve_match_context *mctx, const char *val, size_t val_size ATTR_UNUSED,
+ struct sieve_stringlist *key_list)
+{
+ const struct sieve_runtime_env *renv = mctx->runenv;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ const struct sieve_comparator *cmp = mctx->comparator;
+ int match;
+
+ if ( !ctx->all_compiled ) {
+ string_t *key_item = NULL;
+ unsigned int i;
+ int ret;
+
+ /* Regular expressions still need to be compiled */
+
+ if ( !array_is_created(&ctx->reg_expressions) )
+ p_array_init(&ctx->reg_expressions, mctx->pool, 16);
+
+ i = 0;
+ match = 0;
+ while ( match == 0 &&
+ (ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {
+
+ T_BEGIN {
+ struct mcht_regex_key *rkey;
+
+ if ( i >= array_count(&ctx->reg_expressions) ) {
+ int cflags;
+
+ rkey = array_append_space(&ctx->reg_expressions);
+
+ /* Configure case-sensitivity according to comparator */
+ if ( sieve_comparator_is(cmp, i_octet_comparator) )
+ cflags = REG_EXTENDED;
+ else if ( sieve_comparator_is(cmp, i_ascii_casemap_comparator) )
+ cflags = REG_EXTENDED | REG_ICASE;
+ else
+ rkey->status = -1; /* Not supported */
+
+ if ( rkey->status >= 0 ) {
+ const char *regex_str = str_c(key_item);
+ int rxret;
+
+ /* Indicate whether match values need to be produced */
+ if ( ctx->nmatch == 0 ) cflags |= REG_NOSUB;
+
+ /* Compile regular expression */
+ if ( (rxret=regcomp(&rkey->regexp, regex_str, cflags)) != 0 ) {
+ sieve_runtime_error(renv, NULL,
+ "invalid regular expression '%s' for regex match: %s",
+ str_sanitize(regex_str, 128),
+ _regexp_error(&rkey->regexp, rxret));
+ rkey->status = -1;
+ } else {
+ rkey->status = 1;
+ }
+ }
+ } else {
+ rkey = array_idx_modifiable(&ctx->reg_expressions, 1);
+ }
+
+ if ( rkey->status > 0 ) {
+ match = mcht_regex_match_key(mctx, val, &rkey->regexp);
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with regex `%s' [id=%d] => %d",
+ str_sanitize(str_c(key_item), 80),
+ array_count(&ctx->reg_expressions)-1, match);
+ }
+ }
+ } T_END;
+
+ i++;
+ }
+
+ if ( ret == 0 ) {
+ ctx->all_compiled = TRUE;
+ } else if ( ret < 0 ) {
+ mctx->exec_status = key_list->exec_status;
+ match = -1;
+ }
+
+ } else {
+ const struct mcht_regex_key *rkeys;
+ unsigned int i, count;
+
+ /* Regular expressions are compiled */
+
+ rkeys = array_get(&ctx->reg_expressions, &count);
+
+ i = 0;
+ match = 0;
+ while ( match == 0 && i < count ) {
+ if ( rkeys[i].status > 0 ) {
+ match = mcht_regex_match_key(mctx, val, &rkeys[i].regexp);
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with compiled regex [id=%d] => %d", i, match);
+ }
+ }
+
+ i++;
+ }
+ }
+
+ return match;
+}
+
+void mcht_regex_match_deinit
+(struct sieve_match_context *mctx)
+{
+ struct mcht_regex_context *ctx = (struct mcht_regex_context *) mctx->data;
+ struct mcht_regex_key *rkeys;
+ unsigned int count, i;
+
+ /* Clean up compiled regular expressions */
+ if ( array_is_created(&ctx->reg_expressions) ) {
+ rkeys = array_get_modifiable(&ctx->reg_expressions, &count);
+ for ( i = 0; i < count; i++ ) {
+ regfree(&rkeys[i].regexp);
+ }
+ }
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/Makefile.am b/pigeonhole/src/lib-sieve/plugins/relational/Makefile.am
new file mode 100644
index 0000000..dd28cc5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LTLIBRARIES = libsieve_ext_relational.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_relational_la_SOURCES = \
+ ext-relational-common.c \
+ mcht-value.c \
+ mcht-count.c \
+ ext-relational.c
+
+noinst_HEADERS = \
+ ext-relational-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in b/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in
new file mode 100644
index 0000000..4977808
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in
@@ -0,0 +1,694 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/relational
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_relational_la_LIBADD =
+am_libsieve_ext_relational_la_OBJECTS = ext-relational-common.lo \
+ mcht-value.lo mcht-count.lo ext-relational.lo
+libsieve_ext_relational_la_OBJECTS = \
+ $(am_libsieve_ext_relational_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-relational-common.Plo \
+ ./$(DEPDIR)/ext-relational.Plo ./$(DEPDIR)/mcht-count.Plo \
+ ./$(DEPDIR)/mcht-value.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_relational_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_relational_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_relational.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_relational_la_SOURCES = \
+ ext-relational-common.c \
+ mcht-value.c \
+ mcht-count.c \
+ ext-relational.c
+
+noinst_HEADERS = \
+ ext-relational-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/relational/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/relational/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_relational.la: $(libsieve_ext_relational_la_OBJECTS) $(libsieve_ext_relational_la_DEPENDENCIES) $(EXTRA_libsieve_ext_relational_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_relational_la_OBJECTS) $(libsieve_ext_relational_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-relational-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-relational.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-count.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcht-value.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-relational-common.Plo
+ -rm -f ./$(DEPDIR)/ext-relational.Plo
+ -rm -f ./$(DEPDIR)/mcht-count.Plo
+ -rm -f ./$(DEPDIR)/mcht-value.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-relational-common.Plo
+ -rm -f ./$(DEPDIR)/ext-relational.Plo
+ -rm -f ./$(DEPDIR)/mcht-count.Plo
+ -rm -f ./$(DEPDIR)/mcht-value.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.c b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.c
new file mode 100644
index 0000000..a6dd1ed
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.c
@@ -0,0 +1,171 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Syntax:
+
+ MATCH-TYPE =/ COUNT / VALUE
+ COUNT = ":count" relational-match
+ VALUE = ":value" relational-match
+ relational-match = DQUOTE ( "gt" / "ge" / "lt"
+ / "le" / "eq" / "ne" ) DQUOTE
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Forward declarations
+ */
+
+const struct sieve_match_type_def *rel_match_types[];
+
+/*
+ * Validation
+ */
+
+bool mcht_relational_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_match_type_context *ctx)
+{
+ struct sieve_match_type *mcht;
+ enum relational_match rel_match = REL_MATCH_INVALID;
+ pool_t pool = sieve_ast_argument_pool(ctx->argument);
+ string_t *rel_match_ident;
+
+ /* Check syntax:
+ relational-match = DQUOTE ( "gt" / "ge" / "lt"
+ / "le" / "eq" / "ne" ) DQUOTE
+
+ So, actually this must be a constant string and it is implemented as
+ such.
+ */
+
+ /* Did we get a string in the first place? */
+ if (*arg == NULL || (*arg)->type != SAAT_STRING) {
+ sieve_argument_validate_error(
+ valdtr, (*arg == NULL ? ctx->argument : *arg),
+ "the :%s match-type requires a constant string argument being "
+ "one of \"gt\", \"ge\", \"lt\", \"le\", \"eq\" or \"ne\", "
+ "but %s was found",
+ sieve_match_type_name(ctx->match_type),
+ (*arg == NULL ?
+ "none" : sieve_ast_argument_name(*arg)));
+ return FALSE;
+ }
+
+ /* Check the relational match id */
+
+ rel_match_ident = sieve_ast_argument_str(*arg);
+ if (str_len(rel_match_ident) == 2) {
+ const char *rel_match_id = str_c(rel_match_ident);
+
+ switch (rel_match_id[0]) {
+ /* "gt" or "ge" */
+ case 'g':
+ switch (rel_match_id[1]) {
+ case 't':
+ rel_match = REL_MATCH_GREATER;
+ break;
+ case 'e':
+ rel_match = REL_MATCH_GREATER_EQUAL;
+ break;
+ default:
+ rel_match = REL_MATCH_INVALID;
+ }
+ break;
+ /* "lt" or "le" */
+ case 'l':
+ switch (rel_match_id[1]) {
+ case 't':
+ rel_match = REL_MATCH_LESS;
+ break;
+ case 'e':
+ rel_match = REL_MATCH_LESS_EQUAL;
+ break;
+ default:
+ rel_match = REL_MATCH_INVALID;
+ }
+ break;
+ /* "eq" */
+ case 'e':
+ if (rel_match_id[1] == 'q')
+ rel_match = REL_MATCH_EQUAL;
+ else
+ rel_match = REL_MATCH_INVALID;
+ break;
+ /* "ne" */
+ case 'n':
+ if (rel_match_id[1] == 'e')
+ rel_match = REL_MATCH_NOT_EQUAL;
+ else
+ rel_match = REL_MATCH_INVALID;
+ break;
+ /* invalid */
+ default:
+ rel_match = REL_MATCH_INVALID;
+ }
+ }
+
+ if (rel_match >= REL_MATCH_INVALID) {
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "the :%s match-type requires a constant string argument being "
+ "one of \"gt\", \"ge\", \"lt\", \"le\", \"eq\" or \"ne\", "
+ "but \"%s\" was found",
+ sieve_match_type_name(ctx->match_type),
+ str_sanitize(str_c(rel_match_ident), 32));
+ return FALSE;
+ }
+
+ /* Delete argument */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Not used just yet */
+ ctx->ctx_data = (void *) rel_match;
+
+ /* Override the actual match type with a parameter-specific one
+ * FIXME: ugly!
+ */
+ mcht = p_new(pool, struct sieve_match_type, 1);
+ mcht->object.ext = ctx->match_type->object.ext;
+ SIEVE_OBJECT_SET_DEF(mcht, rel_match_types[
+ REL_MATCH_INDEX(ctx->match_type->object.def->code, rel_match)]);
+ ctx->match_type = mcht;
+
+ return TRUE;
+}
+
+/*
+ * Relational match-type operand
+ */
+
+const struct sieve_match_type_def *rel_match_types[] = {
+ &rel_match_value_gt, &rel_match_value_ge, &rel_match_value_lt,
+ &rel_match_value_le, &rel_match_value_eq, &rel_match_value_ne,
+ &rel_match_count_gt, &rel_match_count_ge, &rel_match_count_lt,
+ &rel_match_count_le, &rel_match_count_eq, &rel_match_count_ne,
+};
+
+static const struct sieve_extension_objects ext_match_types =
+ SIEVE_EXT_DEFINE_MATCH_TYPES(rel_match_types);
+
+const struct sieve_operand_def rel_match_type_operand = {
+ .name = "relational match",
+ .ext_def = &relational_extension,
+ .class = &sieve_match_type_operand_class,
+ .interface = &ext_match_types,
+};
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.h b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.h
new file mode 100644
index 0000000..c0f7d3b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational-common.h
@@ -0,0 +1,86 @@
+#ifndef EXT_RELATIONAL_COMMON_H
+#define EXT_RELATIONAL_COMMON_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+/*
+ * Types
+ */
+
+enum ext_relational_match_type {
+ RELATIONAL_VALUE,
+ RELATIONAL_COUNT
+};
+
+enum relational_match {
+ REL_MATCH_GREATER,
+ REL_MATCH_GREATER_EQUAL,
+ REL_MATCH_LESS,
+ REL_MATCH_LESS_EQUAL,
+ REL_MATCH_EQUAL,
+ REL_MATCH_NOT_EQUAL,
+ REL_MATCH_INVALID
+};
+
+#define REL_MATCH_INDEX(type, match) (type * REL_MATCH_INVALID + match)
+#define REL_MATCH_TYPE(index) (index / REL_MATCH_INVALID)
+#define REL_MATCH(index) (index % REL_MATCH_INVALID)
+
+/*
+ * Extension definitions
+ */
+
+extern const struct sieve_extension_def relational_extension;
+
+/*
+ * Match types
+ */
+
+/* Registered for validation */
+
+extern const struct sieve_match_type_def value_match_type;
+extern const struct sieve_match_type_def count_match_type;
+
+/* Used in byte code */
+
+extern const struct sieve_match_type_def rel_match_count_gt;
+extern const struct sieve_match_type_def rel_match_count_ge;
+extern const struct sieve_match_type_def rel_match_count_lt;
+extern const struct sieve_match_type_def rel_match_count_le;
+extern const struct sieve_match_type_def rel_match_count_eq;
+extern const struct sieve_match_type_def rel_match_count_ne;
+
+extern const struct sieve_match_type_def rel_match_value_gt;
+extern const struct sieve_match_type_def rel_match_value_ge;
+extern const struct sieve_match_type_def rel_match_value_lt;
+extern const struct sieve_match_type_def rel_match_value_le;
+extern const struct sieve_match_type_def rel_match_value_eq;
+extern const struct sieve_match_type_def rel_match_value_ne;
+
+/*
+ * Operand
+ */
+
+extern const struct sieve_operand_def rel_match_type_operand;
+
+
+/*
+ * Match type validation
+ */
+
+bool mcht_relational_validate(struct sieve_validator *validator,
+ struct sieve_ast_argument **arg,
+ struct sieve_match_type_context *ctx);
+
+/*
+ * Value match function (also used by :count)
+ */
+
+int mcht_value_match_key(struct sieve_match_context *mctx,
+ const char *val, size_t val_size,
+ const char *key, size_t key_size);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/ext-relational.c b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational.c
new file mode 100644
index 0000000..28c278f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/ext-relational.c
@@ -0,0 +1,53 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension relational
+ * --------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 3431
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_relational_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def relational_extension = {
+ .name = "relational",
+ .validator_load = ext_relational_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(rel_match_type_operand)
+};
+
+static bool ext_relational_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_match_type_register(valdtr, ext, &value_match_type);
+ sieve_match_type_register(valdtr, ext, &count_match_type);
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/mcht-count.c b/pigeonhole/src/lib-sieve/plugins/relational/mcht-count.c
new file mode 100644
index 0000000..e31a63a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/mcht-count.c
@@ -0,0 +1,119 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Match-type ':count'
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-match.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Forward declarations
+ */
+
+static int mcht_count_match
+ (struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+ struct sieve_stringlist *key_list);
+
+/*
+ * Match-type objects
+ */
+
+const struct sieve_match_type_def count_match_type = {
+ SIEVE_OBJECT("count",
+ &rel_match_type_operand, RELATIONAL_COUNT),
+ .validate = mcht_relational_validate
+};
+
+#define COUNT_MATCH_TYPE(name, rel_match) \
+const struct sieve_match_type_def rel_match_count_ ## name = { \
+ SIEVE_OBJECT("count-" #name, \
+ &rel_match_type_operand, \
+ REL_MATCH_INDEX(RELATIONAL_COUNT, rel_match)), \
+ .match = mcht_count_match, \
+}
+
+COUNT_MATCH_TYPE(gt, REL_MATCH_GREATER);
+COUNT_MATCH_TYPE(ge, REL_MATCH_GREATER_EQUAL);
+COUNT_MATCH_TYPE(lt, REL_MATCH_LESS);
+COUNT_MATCH_TYPE(le, REL_MATCH_LESS_EQUAL);
+COUNT_MATCH_TYPE(eq, REL_MATCH_EQUAL);
+COUNT_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
+
+/*
+ * Match-type implementation
+ */
+
+static int mcht_count_match
+(struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+ struct sieve_stringlist *key_list)
+{
+ const struct sieve_runtime_env *renv = mctx->runenv;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ int count;
+ string_t *key_item;
+ int match, ret;
+
+ if ( (count=sieve_stringlist_get_length(value_list)) < 0 ) {
+ mctx->exec_status = value_list->exec_status;
+ return -1;
+ }
+
+ sieve_stringlist_reset(key_list);
+
+ string_t *value = t_str_new(20);
+ str_printfa(value, "%d", count);
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "matching count value `%s'", str_sanitize(str_c(value), 80));
+ }
+
+ sieve_runtime_trace_descend(renv);
+
+ /* Match to all key values */
+ key_item = NULL;
+ match = 0;
+ while ( match == 0 &&
+ (ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 )
+ {
+ match = mcht_value_match_key
+ (mctx, str_c(value), str_len(value), str_c(key_item), str_len(key_item));
+
+ if ( trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with key `%s' => %d", str_sanitize(str_c(key_item), 80), ret);
+ }
+ }
+
+ sieve_runtime_trace_ascend(renv);
+
+ if ( ret < 0 ) {
+ mctx->exec_status = key_list->exec_status;
+ match = -1;
+ }
+
+ return match;
+}
+
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/relational/mcht-value.c b/pigeonhole/src/lib-sieve/plugins/relational/mcht-value.c
new file mode 100644
index 0000000..dd55590
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/relational/mcht-value.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ast.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-match.h"
+
+#include "ext-relational-common.h"
+
+/*
+ * Match-type objects
+ */
+
+const struct sieve_match_type_def value_match_type = {
+ SIEVE_OBJECT("value",
+ &rel_match_type_operand, RELATIONAL_VALUE),
+ .validate = mcht_relational_validate
+};
+
+#define VALUE_MATCH_TYPE(name, rel_match) \
+const struct sieve_match_type_def rel_match_value_ ## name = { \
+ SIEVE_OBJECT("value-" #name, \
+ &rel_match_type_operand, \
+ REL_MATCH_INDEX(RELATIONAL_VALUE, rel_match)), \
+ .match_key = mcht_value_match_key, \
+}
+
+VALUE_MATCH_TYPE(gt, REL_MATCH_GREATER);
+VALUE_MATCH_TYPE(ge, REL_MATCH_GREATER_EQUAL);
+VALUE_MATCH_TYPE(lt, REL_MATCH_LESS);
+VALUE_MATCH_TYPE(le, REL_MATCH_LESS_EQUAL);
+VALUE_MATCH_TYPE(eq, REL_MATCH_EQUAL);
+VALUE_MATCH_TYPE(ne, REL_MATCH_NOT_EQUAL);
+
+/*
+ * Match-type implementation
+ */
+
+int mcht_value_match_key
+(struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size)
+{
+ const struct sieve_match_type *mtch = mctx->match_type;
+ unsigned int rel_match = REL_MATCH(mtch->object.def->code);
+ int cmp_result;
+
+ cmp_result = mctx->comparator->def->
+ compare(mctx->comparator, val, val_size, key, key_size);
+
+ switch ( rel_match ) {
+ case REL_MATCH_GREATER:
+ return ( cmp_result > 0 ? 1 : 0 );
+ case REL_MATCH_GREATER_EQUAL:
+ return ( cmp_result >= 0 ? 1 : 0 );
+ case REL_MATCH_LESS:
+ return ( cmp_result < 0 ? 1 : 0 );
+ case REL_MATCH_LESS_EQUAL:
+ return ( cmp_result <= 0 ? 1 : 0 );
+ case REL_MATCH_EQUAL:
+ return ( cmp_result == 0 ? 1 : 0);
+ case REL_MATCH_NOT_EQUAL:
+ return ( cmp_result != 0 ? 1 : 0);
+ }
+
+ i_unreached();
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.am b/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.am
new file mode 100644
index 0000000..6a15bdd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_spamvirustest.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-spamvirustest.c
+
+libsieve_ext_spamvirustest_la_SOURCES = \
+ $(tests) \
+ ext-spamvirustest-common.c \
+ ext-spamvirustest.c
+
+noinst_HEADERS = \
+ ext-spamvirustest-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in b/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in
new file mode 100644
index 0000000..dca31e3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in
@@ -0,0 +1,694 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/spamvirustest
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_spamvirustest_la_LIBADD =
+am__objects_1 = tst-spamvirustest.lo
+am_libsieve_ext_spamvirustest_la_OBJECTS = $(am__objects_1) \
+ ext-spamvirustest-common.lo ext-spamvirustest.lo
+libsieve_ext_spamvirustest_la_OBJECTS = \
+ $(am_libsieve_ext_spamvirustest_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-spamvirustest-common.Plo \
+ ./$(DEPDIR)/ext-spamvirustest.Plo \
+ ./$(DEPDIR)/tst-spamvirustest.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_spamvirustest_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_spamvirustest_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_spamvirustest.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tests = \
+ tst-spamvirustest.c
+
+libsieve_ext_spamvirustest_la_SOURCES = \
+ $(tests) \
+ ext-spamvirustest-common.c \
+ ext-spamvirustest.c
+
+noinst_HEADERS = \
+ ext-spamvirustest-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/spamvirustest/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/spamvirustest/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_spamvirustest.la: $(libsieve_ext_spamvirustest_la_OBJECTS) $(libsieve_ext_spamvirustest_la_DEPENDENCIES) $(EXTRA_libsieve_ext_spamvirustest_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_spamvirustest_la_OBJECTS) $(libsieve_ext_spamvirustest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-spamvirustest-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-spamvirustest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-spamvirustest.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-spamvirustest-common.Plo
+ -rm -f ./$(DEPDIR)/ext-spamvirustest.Plo
+ -rm -f ./$(DEPDIR)/tst-spamvirustest.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-spamvirustest-common.Plo
+ -rm -f ./$(DEPDIR)/ext-spamvirustest.Plo
+ -rm -f ./$(DEPDIR)/tst-spamvirustest.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
new file mode 100644
index 0000000..38cb5d8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.c
@@ -0,0 +1,674 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+
+#include "ext-spamvirustest-common.h"
+
+#include <sys/types.h>
+#include <regex.h>
+#include <ctype.h>
+
+/*
+ * Extension data
+ */
+
+enum ext_spamvirustest_status_type {
+ EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE,
+ EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN,
+ EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT,
+};
+
+struct ext_spamvirustest_header_spec {
+ const char *header_name;
+ regex_t regexp;
+ bool regexp_match;
+};
+
+struct ext_spamvirustest_data {
+ pool_t pool;
+
+ int reload;
+
+ struct ext_spamvirustest_header_spec status_header;
+ struct ext_spamvirustest_header_spec max_header;
+
+ enum ext_spamvirustest_status_type status_type;
+
+ float max_value;
+
+ const char *text_values[11];
+};
+
+/*
+ * Regexp utility
+ */
+
+static bool _regexp_compile
+(regex_t *regexp, const char *data, const char **error_r)
+{
+ size_t errsize;
+ int ret;
+
+ *error_r = "";
+
+ if ( (ret=regcomp(regexp, data, REG_EXTENDED)) == 0 ) {
+ return TRUE;
+ }
+
+ errsize = regerror(ret, regexp, NULL, 0);
+
+ if ( errsize > 0 ) {
+ char *errbuf = t_malloc0(errsize);
+
+ (void)regerror(ret, regexp, errbuf, errsize);
+
+ /* We don't want the error to start with a capital letter */
+ errbuf[0] = i_tolower(errbuf[0]);
+
+ *error_r = errbuf;
+ }
+
+ return FALSE;
+}
+
+static const char *_regexp_match_get_value
+(const char *string, int index, regmatch_t pmatch[], int nmatch)
+{
+ if ( index > -1 && index < nmatch && pmatch[index].rm_so != -1 ) {
+ return t_strndup(string + pmatch[index].rm_so,
+ pmatch[index].rm_eo - pmatch[index].rm_so);
+ }
+ return NULL;
+}
+
+/*
+ * Configuration parser
+ */
+
+static bool ext_spamvirustest_header_spec_parse
+(struct ext_spamvirustest_header_spec *spec, pool_t pool, const char *data,
+ const char **error_r)
+{
+ const char *p;
+ const char *regexp_error;
+
+ if ( *data == '\0' ) {
+ *error_r = "empty header specification";
+ return FALSE;
+ }
+
+ /* Parse header name */
+
+ p = data;
+
+ while ( *p == ' ' || *p == '\t' ) p++;
+ while ( *p != ':' && *p != '\0' && *p != ' ' && *p != '\t' ) p++;
+
+ if ( *p == '\0' ) {
+ spec->header_name = p_strdup(pool, data);
+ return TRUE;
+ }
+
+ spec->header_name = p_strdup_until(pool, data, p);
+ while ( *p == ' ' || *p == '\t' ) p++;
+
+ if ( *p == '\0' ) {
+ spec->regexp_match = FALSE;
+ return TRUE;
+ }
+
+ /* Parse and compile regular expression */
+
+ if ( *p != ':' ) {
+ *error_r = t_strdup_printf("expecting ':', but found '%c'", *p);
+ return FALSE;
+ }
+ p++;
+ while ( *p == ' ' || *p == '\t' ) p++;
+
+ spec->regexp_match = TRUE;
+ if ( !_regexp_compile(&spec->regexp, p, &regexp_error) ) {
+ *error_r = t_strdup_printf("failed to compile regular expression '%s': "
+ "%s", p, regexp_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void ext_spamvirustest_header_spec_free
+(struct ext_spamvirustest_header_spec *spec)
+{
+ regfree(&spec->regexp);
+}
+
+static bool ext_spamvirustest_parse_strlen_value
+(const char *str_value, float *value_r, const char **error_r)
+{
+ const char *p = str_value;
+ char ch = *p;
+
+ if ( *str_value == '\0' ) {
+ *value_r = 0;
+ return TRUE;
+ }
+
+ while ( *p == ch ) p++;
+
+ if ( *p != '\0' ) {
+ *error_r = t_strdup_printf(
+ "different character '%c' encountered in strlen value",
+ *p);
+ return FALSE;
+ }
+
+ *value_r = ( p - str_value );
+
+ return TRUE;
+}
+
+static bool ext_spamvirustest_parse_decimal_value
+(const char *str_value, float *value_r, const char **error_r)
+{
+ const char *p = str_value;
+ float value;
+ float sign = 1;
+ int digits;
+
+ if ( *p == '\0' ) {
+ *error_r = "empty value";
+ return FALSE;
+ }
+
+ if ( *p == '+' || *p == '-' ) {
+ if ( *p == '-' )
+ sign = -1;
+
+ p++;
+ }
+
+ value = 0;
+ digits = 0;
+ while ( i_isdigit(*p) ) {
+ value = value*10 + (*p-'0');
+ if ( digits++ > 4 ) {
+ *error_r = t_strdup_printf
+ ("decimal value has too many digits before radix point: %s",
+ str_value);
+ return FALSE;
+ }
+ p++;
+ }
+
+ if ( *p == '.' || *p == ',' ) {
+ float radix = .1;
+ p++;
+
+ digits = 0;
+ while ( i_isdigit(*p) ) {
+ value = value + (*p-'0')*radix;
+
+ if ( digits++ > 4 ) {
+ *error_r = t_strdup_printf
+ ("decimal value has too many digits after radix point: %s",
+ str_value);
+ return FALSE;
+ }
+ radix /= 10;
+ p++;
+ }
+ }
+
+ if ( *p != '\0' ) {
+ *error_r = t_strdup_printf
+ ("invalid decimal point value: %s", str_value);
+ return FALSE;
+ }
+
+ *value_r = value * sign;
+
+ return TRUE;
+}
+
+/*
+ * Extension initialization
+ */
+
+bool ext_spamvirustest_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct ext_spamvirustest_data *ext_data =
+ (struct ext_spamvirustest_data *) *context;
+ struct sieve_instance *svinst = ext->svinst;
+ const char *ext_name, *status_header, *max_header, *status_type,
+ *max_value;
+ enum ext_spamvirustest_status_type type;
+ const char *error;
+ pool_t pool;
+ bool result = TRUE;
+ int reload = 0;
+
+ if ( *context != NULL ) {
+ reload = ext_data->reload + 1;
+ ext_spamvirustest_unload(ext);
+ *context = NULL;
+ }
+
+ /* FIXME:
+ * Prevent loading of both spamtest and spamtestplus: let these share
+ * contexts.
+ */
+
+ if ( sieve_extension_is(ext, spamtest_extension) ||
+ sieve_extension_is(ext, spamtestplus_extension) ) {
+ ext_name = spamtest_extension.name;
+ } else {
+ ext_name = sieve_extension_name(ext);
+ }
+
+ /* Get settings */
+
+ status_header = sieve_setting_get
+ (svinst, t_strconcat("sieve_", ext_name, "_status_header", NULL));
+ status_type = sieve_setting_get
+ (svinst, t_strconcat("sieve_", ext_name, "_status_type", NULL));
+ max_header = sieve_setting_get
+ (svinst, t_strconcat("sieve_", ext_name, "_max_header", NULL));
+ max_value = sieve_setting_get
+ (svinst, t_strconcat("sieve_", ext_name, "_max_value", NULL));
+
+ /* Base configuration */
+
+ if ( status_header == NULL ) {
+ return TRUE;
+ }
+
+ if ( status_type == NULL || strcmp(status_type, "score") == 0 ) {
+ type = EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE;
+ } else if ( strcmp(status_type, "strlen") == 0 ) {
+ type = EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN;
+ } else if ( strcmp(status_type, "text") == 0 ) {
+ type = EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT;
+ } else {
+ e_error(svinst->event, "%s: "
+ "invalid status type '%s'", ext_name, status_type);
+ return FALSE;
+ }
+
+ /* Verify settings */
+
+ if ( type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+
+ if ( max_header != NULL && max_value != NULL ) {
+ e_error(svinst->event, "%s: "
+ "sieve_%s_max_header and sieve_%s_max_value "
+ "cannot both be configured",
+ ext_name, ext_name, ext_name);
+ return TRUE;
+ }
+
+ if ( max_header == NULL && max_value == NULL ) {
+ e_error(svinst->event, "%s: "
+ "none of sieve_%s_max_header or sieve_%s_max_value "
+ "is configured", ext_name, ext_name, ext_name);
+ return TRUE;
+ }
+ } else {
+ if ( max_header != NULL ) {
+ e_warning(svinst->event, "%s: "
+ "setting sieve_%s_max_header has no meaning "
+ "for sieve_%s_status_type=text",
+ ext_name, ext_name, ext_name);
+ }
+
+ if ( max_value != NULL ) {
+ e_warning(svinst->event, "%s: "
+ "setting sieve_%s_max_value has no meaning "
+ "for sieve_%s_status_type=text",
+ ext_name, ext_name, ext_name);
+ }
+ }
+
+ pool = pool_alloconly_create("spamvirustest_data", 512);
+ ext_data = p_new(pool, struct ext_spamvirustest_data, 1);
+ ext_data->pool = pool;
+ ext_data->reload = reload;
+ ext_data->status_type = type;
+
+ if ( !ext_spamvirustest_header_spec_parse
+ (&ext_data->status_header, ext_data->pool, status_header, &error) ) {
+ e_error(svinst->event, "%s: "
+ "invalid status header specification '%s': %s",
+ ext_name, status_header, error);
+ result = FALSE;
+ }
+
+ if ( result ) {
+ if ( type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+ /* Parse max header */
+
+ if ( max_header != NULL && !ext_spamvirustest_header_spec_parse
+ (&ext_data->max_header, ext_data->pool, max_header, &error) ) {
+ e_error(svinst->event, "%s: "
+ "invalid max header specification "
+ "'%s': %s", ext_name, max_header,
+ error);
+ result = FALSE;
+ }
+
+ /* Parse max value */
+
+ if ( result && max_value != NULL ) {
+ if ( !ext_spamvirustest_parse_decimal_value
+ (max_value, &ext_data->max_value, &error) ) {
+ e_error(svinst->event, "%s: "
+ "invalid max value specification "
+ "'%s': %s", ext_name, max_value,
+ error);
+ result = FALSE;
+ }
+ }
+
+ } else {
+ unsigned int i, max_text;
+
+ max_text = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+
+ /* Get text values */
+ for ( i = 0; i <= max_text; i++ ) {
+ const char *value = sieve_setting_get
+ (svinst, t_strdup_printf("sieve_%s_text_value%d", ext_name, i));
+
+ if ( value != NULL && *value != '\0' )
+ ext_data->text_values[i] = p_strdup(ext_data->pool, value);
+ }
+
+ ext_data->max_value = 1;
+ }
+ }
+
+ if ( result ) {
+ *context = (void *) ext_data;
+ } else {
+ e_warning(svinst->event, "%s: "
+ "extension not configured, "
+ "tests will always match against \"0\"",
+ ext_name);
+ ext_spamvirustest_unload(ext);
+ *context = NULL;
+ }
+
+ return result;
+}
+
+void ext_spamvirustest_unload(const struct sieve_extension *ext)
+{
+ struct ext_spamvirustest_data *ext_data =
+ (struct ext_spamvirustest_data *) ext->context;
+
+ if ( ext_data != NULL ) {
+ ext_spamvirustest_header_spec_free(&ext_data->status_header);
+ ext_spamvirustest_header_spec_free(&ext_data->max_header);
+ pool_unref(&ext_data->pool);
+ }
+}
+
+/*
+ * Runtime
+ */
+
+struct ext_spamvirustest_message_context {
+ int reload;
+ float score_ratio;
+};
+
+static const char *ext_spamvirustest_get_score
+(const struct sieve_extension *ext, float score_ratio, bool percent)
+{
+ int score;
+
+ if ( score_ratio < 0 )
+ return "0";
+
+ if ( score_ratio > 1 )
+ score_ratio = 1;
+
+ if ( percent )
+ score = score_ratio * 100 + 0.001;
+ else if ( sieve_extension_is(ext, virustest_extension) )
+ score = score_ratio * 4 + 1.001;
+ else
+ score = score_ratio * 9 + 1.001;
+
+ return t_strdup_printf("%d", score);
+}
+
+int ext_spamvirustest_get_value
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+ bool percent, const char **value_r)
+{
+ struct ext_spamvirustest_data *ext_data =
+ (struct ext_spamvirustest_data *) ext->context;
+ struct ext_spamvirustest_header_spec *status_header, *max_header;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct ext_spamvirustest_message_context *mctx;
+ struct mail *mail;
+ regmatch_t match_values[2];
+ const char *header_value, *error;
+ const char *status = NULL, *max = NULL;
+ float status_value, max_value;
+ unsigned int i, max_text;
+ pool_t pool = sieve_interpreter_pool(renv->interp);
+
+ *value_r = "0";
+
+ /*
+ * Check whether extension is properly configured
+ */
+ if ( ext_data == NULL ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "error: extension not configured");
+ return SIEVE_EXEC_OK;
+ }
+
+ /*
+ * Check wether a cached result is available
+ */
+
+ mctx = (struct ext_spamvirustest_message_context *)
+ sieve_message_context_extension_get(msgctx, ext);
+
+ if ( mctx == NULL ) {
+ /* Create new context */
+ mctx = p_new(pool, struct ext_spamvirustest_message_context, 1);
+ sieve_message_context_extension_set(msgctx, ext, (void *)mctx);
+ } else if ( mctx->reload == ext_data->reload ) {
+ /* Use cached result */
+ *value_r = ext_spamvirustest_get_score(ext, mctx->score_ratio, percent);
+ return SIEVE_EXEC_OK;
+ } else {
+ /* Extension was reloaded (probably in testsuite) */
+ }
+
+ mctx->reload = ext_data->reload;
+
+ /*
+ * Get max status value
+ */
+
+ mail = sieve_message_get_mail(renv->msgctx);
+ status_header = &ext_data->status_header;
+ max_header = &ext_data->max_header;
+
+ if ( ext_data->status_type != EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT ) {
+ if ( max_header->header_name != NULL ) {
+ /* Get header from message */
+ if ( mail_get_first_header_utf8
+ (mail, max_header->header_name, &header_value) < 0 ) {
+ return sieve_runtime_mail_error (renv, mail,
+ "%s test: failed to read header field `%s'",
+ sieve_extension_name(ext), max_header->header_name);
+ }
+ if ( header_value == NULL ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "header '%s' not found in message",
+ max_header->header_name);
+ goto failed;
+ }
+
+ if ( max_header->regexp_match ) {
+ /* Execute regex */
+ if ( regexec(&max_header->regexp, header_value, 2, match_values, 0)
+ != 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "regexp for header '%s' did not match "
+ "on value '%s'", max_header->header_name, header_value);
+ goto failed;
+ }
+
+ max = _regexp_match_get_value(header_value, 1, match_values, 2);
+ if ( max == NULL ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "regexp did not return match value "
+ "for string '%s'", header_value);
+ goto failed;
+ }
+ } else {
+ max = header_value;
+ }
+
+ if ( !ext_spamvirustest_parse_decimal_value(max, &max_value, &error) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "failed to parse maximum value: %s", error);
+ goto failed;
+ }
+ } else {
+ max_value = ext_data->max_value;
+ }
+
+ if ( max_value == 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "error: max value is 0");
+ goto failed;
+ }
+ } else {
+ max_value = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+ }
+
+ /*
+ * Get status value
+ */
+
+ /* Get header from message */
+ if ( mail_get_first_header_utf8
+ (mail, status_header->header_name, &header_value) < 0 ) {
+ return sieve_runtime_mail_error (renv, mail,
+ "%s test: failed to read header field `%s'",
+ sieve_extension_name(ext), status_header->header_name);
+ }
+ if ( header_value == NULL ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "header '%s' not found in message",
+ status_header->header_name);
+ goto failed;
+ }
+
+ /* Execute regex */
+ if ( status_header->regexp_match ) {
+ if ( regexec(&status_header->regexp, header_value, 2, match_values, 0)
+ != 0 ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "regexp for header '%s' did not match on value '%s'",
+ status_header->header_name, header_value);
+ goto failed;
+ }
+
+ status = _regexp_match_get_value(header_value, 1, match_values, 2);
+ if ( status == NULL ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "regexp did not return match value for string '%s'",
+ header_value);
+ goto failed;
+ }
+ } else {
+ status = header_value;
+ }
+
+ switch ( ext_data->status_type ) {
+ case EXT_SPAMVIRUSTEST_STATUS_TYPE_SCORE:
+ if ( !ext_spamvirustest_parse_decimal_value
+ (status, &status_value, &error) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "failed to parse status value '%s': %s",
+ status, error);
+ goto failed;
+ }
+ break;
+ case EXT_SPAMVIRUSTEST_STATUS_TYPE_STRLEN:
+ if ( !ext_spamvirustest_parse_strlen_value
+ (status, &status_value, &error) ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "failed to parse status value '%s': %s",
+ status, error);
+ goto failed;
+ }
+ break;
+ case EXT_SPAMVIRUSTEST_STATUS_TYPE_TEXT:
+ max_text = ( sieve_extension_is(ext, virustest_extension) ? 5 : 10 );
+ status_value = 0;
+
+ i = 0;
+ while ( i <= max_text ) {
+ if ( ext_data->text_values[i] != NULL &&
+ strcmp(status, ext_data->text_values[i]) == 0 ) {
+ status_value = (float) i;
+ break;
+ }
+ i++;
+ }
+
+ if ( i > max_text ) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "failed to match textstatus value '%s'",
+ status);
+ goto failed;
+ }
+ break;
+ default:
+ i_unreached();
+ break;
+ }
+
+ /* Calculate value */
+ if ( status_value < 0 )
+ mctx->score_ratio = 0;
+ else if ( status_value > max_value )
+ mctx->score_ratio = 1;
+ else
+ mctx->score_ratio = (status_value / max_value);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "extracted score=%.3f, max=%.3f, ratio=%.0f %%",
+ status_value, max_value, mctx->score_ratio * 100);
+
+ *value_r = ext_spamvirustest_get_score(ext, mctx->score_ratio, percent);
+ return SIEVE_EXEC_OK;
+
+failed:
+ mctx->score_ratio = -1;
+ *value_r = "0";
+ return SIEVE_EXEC_OK;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.h b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.h
new file mode 100644
index 0000000..331a637
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest-common.h
@@ -0,0 +1,35 @@
+#ifndef EXT_SPAMVIRUSTEST_COMMON_H
+#define EXT_SPAMVIRUSTEST_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def spamtest_extension;
+extern const struct sieve_extension_def spamtestplus_extension;
+extern const struct sieve_extension_def virustest_extension;
+
+bool ext_spamvirustest_load(const struct sieve_extension *ext, void **context);
+void ext_spamvirustest_unload(const struct sieve_extension *ext);
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def spamtest_test;
+extern const struct sieve_command_def virustest_test;
+
+int ext_spamvirustest_get_value
+(const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+ bool percent, const char **value_r);
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def spamtest_operation;
+extern const struct sieve_operation_def virustest_operation;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest.c b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest.c
new file mode 100644
index 0000000..e0c9b54
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/ext-spamvirustest.c
@@ -0,0 +1,146 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extensions spamtest, spamtestplus and virustest
+ * -----------------------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5235
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+/* Configuration examples:
+ *
+ * # 1: X-Spam-Score: No, score=-3.2
+ *
+ * sieve_spamtest_status_header = \
+ * X-Spam-Score: [[:alnum:]]+, score=(-?[[:digit:]]+\.[[:digit:]])
+ * sieve_spamtest_max_value = 5.0
+ *
+ * # 2: X-Spam-Status: Yes
+ *
+ * sieve_spamtest_status_header = X-Spam-Status
+ * sieve_spamtest_status_type = yesno
+ * sieve_spamtest_max_value = Yes
+ *
+ * # 3: X-Spam-Score: sssssss
+ * sieve_spamtest_status_header = X-Spam-Score
+ * sieve_spamtest_status_type = strlen
+ * sieve_spamtest_max_value = 5
+ *
+ * # 4: X-Spam-Score: status=3.2 required=5.0
+ *
+ * sieve_spamtest_status_header = \
+ * X-Spam-Score: score=(-?[[:digit:]]+\.[[:digit:]]).*
+ * sieve_spamtest_max_header = \
+ * X-Spam-Score: score=-?[[:digit:]]+\.[[:digit:]] required=([[:digit:]]+\.[[:digit:]])
+ *
+ * # 5: X-Virus-Scan: Found to be clean.
+ *
+ * sieve_virustest_status_header = \
+ * X-Virus-Scan: Found to be (.+)\.
+ * sieve_virustest_status_type = text
+ * sieve_virustest_text_value1 = clean
+ * sieve_virustest_text_value5 = infected
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+
+#include "sieve-validator.h"
+
+#include "ext-spamvirustest-common.h"
+
+/*
+ * Extensions
+ */
+
+/* Spamtest */
+
+static bool ext_spamvirustest_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def spamtest_extension = {
+ .name = "spamtest",
+ .load = ext_spamvirustest_load,
+ .unload = ext_spamvirustest_unload,
+ .validator_load = ext_spamvirustest_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(spamtest_operation)
+};
+
+const struct sieve_extension_def spamtestplus_extension = {
+ .name = "spamtestplus",
+ .load = ext_spamvirustest_load,
+ .unload = ext_spamvirustest_unload,
+ .validator_load = ext_spamvirustest_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(spamtest_operation)
+};
+
+const struct sieve_extension_def virustest_extension = {
+ .name = "virustest",
+ .load = ext_spamvirustest_load,
+ .unload = ext_spamvirustest_unload,
+ .validator_load = ext_spamvirustest_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(virustest_operation)
+};
+
+/*
+ * Implementation
+ */
+
+static bool ext_spamtest_validator_check_conflict
+ (const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required);
+
+const struct sieve_validator_extension spamtest_validator_extension = {
+ .ext = &spamtest_extension,
+ .check_conflict = ext_spamtest_validator_check_conflict
+};
+
+static bool ext_spamvirustest_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new test */
+
+ if ( sieve_extension_is(ext, virustest_extension) ) {
+ sieve_validator_register_command(valdtr, ext, &virustest_test);
+ } else {
+ if ( sieve_extension_is(ext, spamtest_extension) ) {
+ /* Register validator extension to warn for duplicate */
+ sieve_validator_extension_register
+ (valdtr, ext, &spamtest_validator_extension, NULL);
+ }
+
+ sieve_validator_register_command(valdtr, ext, &spamtest_test);
+ }
+
+ return TRUE;
+}
+
+static bool ext_spamtest_validator_check_conflict
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_validator *valdtr, void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required ATTR_UNUSED)
+{
+ if ( sieve_extension_name_is(ext_other, "spamtestplus") ) {
+ sieve_argument_validate_warning(valdtr, require_arg,
+ "the spamtest and spamtestplus extensions should "
+ "not be specified at the same time");
+ }
+
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c b/pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
new file mode 100644
index 0000000..26d051e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/spamvirustest/tst-spamvirustest.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-spamvirustest-common.h"
+
+/*
+ * Tests
+ */
+
+static bool tst_spamvirustest_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_spamvirustest_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+static bool tst_spamvirustest_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+
+/* Spamtest test
+ *
+ * Syntax:
+ * spamtest [":percent"] [COMPARATOR] [MATCH-TYPE] <value: string>
+ */
+
+const struct sieve_command_def spamtest_test = {
+ .identifier = "spamtest",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_spamvirustest_registered,
+ .validate = tst_spamvirustest_validate,
+ .generate = tst_spamvirustest_generate
+};
+
+/* Virustest test
+ *
+ * Syntax:
+ * virustest [COMPARATOR] [MATCH-TYPE] <value: string>
+ */
+
+const struct sieve_command_def virustest_test = {
+ .identifier = "virustest",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_spamvirustest_registered,
+ .validate = tst_spamvirustest_validate,
+ .generate = tst_spamvirustest_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+static bool tst_spamtest_validate_percent_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *tst);
+
+static const struct sieve_argument_def spamtest_percent_tag = {
+ .identifier = "percent",
+ .validate = tst_spamtest_validate_percent_tag
+};
+
+/*
+ * Spamtest and virustest operations
+ */
+
+static bool tst_spamvirustest_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_spamvirustest_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def spamtest_operation = {
+ .mnemonic = "SPAMTEST",
+ .ext_def = &spamtest_extension,
+ .dump = tst_spamvirustest_operation_dump,
+ .execute = tst_spamvirustest_operation_execute
+};
+
+const struct sieve_operation_def virustest_operation = {
+ .mnemonic = "VIRUSTEST",
+ .ext_def = &virustest_extension,
+ .dump = tst_spamvirustest_operation_dump,
+ .execute = tst_spamvirustest_operation_execute
+};
+
+/*
+ * Optional operands
+ */
+
+enum tst_spamvirustest_optional {
+ OPT_SPAMTEST_PERCENT = SIEVE_MATCH_OPT_LAST,
+ OPT_SPAMTEST_LAST
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_spamvirustest_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ if ( sieve_extension_is(ext, spamtestplus_extension) ||
+ sieve_extension_is(ext, spamtest_extension) ) {
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &spamtest_percent_tag, OPT_SPAMTEST_PERCENT);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_spamtest_validate_percent_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *tst)
+{
+ if ( !sieve_extension_is(tst->ext, spamtestplus_extension) ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "the spamtest test only accepts the :percent argument when "
+ "the spamtestplus extension is active");
+ return FALSE;
+ }
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool tst_spamvirustest_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+
+ /* Check value */
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "value", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_spamvirustest_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ if ( sieve_command_is(tst, spamtest_test) )
+ sieve_operation_emit(cgenv->sblock, tst->ext, &spamtest_operation);
+ else if ( sieve_command_is(tst, virustest_test) )
+ sieve_operation_emit(cgenv->sblock, tst->ext, &virustest_operation);
+ else
+ i_unreached();
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_spamvirustest_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ const struct sieve_operation *op = denv->oprtn;
+
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_SPAMTEST_PERCENT:
+ sieve_code_dumpf(denv, "percent");
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ return
+ sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_spamvirustest_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *op = renv->oprtn;
+ const struct sieve_extension *this_ext = op->ext;
+ int opt_code = 0;
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ bool percent = FALSE;
+ struct sieve_stringlist *value_list, *key_list;
+ const char *score_value;
+ int match, ret;
+
+ /* Read optional operands */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_SPAMTEST_PERCENT:
+ percent = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read value part */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "value", &key_list)) <= 0 )
+ return ret;
+
+ /* Perform test */
+
+ if ( sieve_operation_is(op, spamtest_operation) ) {
+ sieve_runtime_trace
+ (renv, SIEVE_TRLVL_TESTS, "spamtest test [percent=%s]",
+ ( percent ? "true" : "false" ));
+ } else {
+ sieve_runtime_trace
+ (renv, SIEVE_TRLVL_TESTS, "virustest test");
+ }
+
+ /* Get score value */
+ sieve_runtime_trace_descend(renv);
+ if ( (ret=ext_spamvirustest_get_value
+ (renv, this_ext, percent, &score_value)) <= 0 )
+ return ret;
+ sieve_runtime_trace_ascend(renv);
+
+ /* Construct value list */
+ value_list = sieve_single_stringlist_create_cstr(renv, score_value, TRUE);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.am b/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.am
new file mode 100644
index 0000000..0f31aee
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.am
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libsieve_ext_special_use.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tags = \
+ tag-specialuse.c
+
+tests = \
+ tst-specialuse-exists.c
+
+libsieve_ext_special_use_la_SOURCES = \
+ $(tags) \
+ $(tests) \
+ ext-special-use-common.c \
+ ext-special-use.c
+
+headers = \
+ ext-special-use-common.h
+
+noinst_HEADERS = $(headers)
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in b/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in
new file mode 100644
index 0000000..5032dae
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in
@@ -0,0 +1,703 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/special-use
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_special_use_la_LIBADD =
+am__objects_1 = tag-specialuse.lo
+am__objects_2 = tst-specialuse-exists.lo
+am_libsieve_ext_special_use_la_OBJECTS = $(am__objects_1) \
+ $(am__objects_2) ext-special-use-common.lo ext-special-use.lo
+libsieve_ext_special_use_la_OBJECTS = \
+ $(am_libsieve_ext_special_use_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-special-use-common.Plo \
+ ./$(DEPDIR)/ext-special-use.Plo ./$(DEPDIR)/tag-specialuse.Plo \
+ ./$(DEPDIR)/tst-specialuse-exists.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_special_use_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_special_use_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_special_use.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+tags = \
+ tag-specialuse.c
+
+tests = \
+ tst-specialuse-exists.c
+
+libsieve_ext_special_use_la_SOURCES = \
+ $(tags) \
+ $(tests) \
+ ext-special-use-common.c \
+ ext-special-use.c
+
+headers = \
+ ext-special-use-common.h
+
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/special-use/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/special-use/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_special_use.la: $(libsieve_ext_special_use_la_OBJECTS) $(libsieve_ext_special_use_la_DEPENDENCIES) $(EXTRA_libsieve_ext_special_use_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_special_use_la_OBJECTS) $(libsieve_ext_special_use_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-special-use-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-special-use.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag-specialuse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-specialuse-exists.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-special-use-common.Plo
+ -rm -f ./$(DEPDIR)/ext-special-use.Plo
+ -rm -f ./$(DEPDIR)/tag-specialuse.Plo
+ -rm -f ./$(DEPDIR)/tst-specialuse-exists.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-special-use-common.Plo
+ -rm -f ./$(DEPDIR)/ext-special-use.Plo
+ -rm -f ./$(DEPDIR)/tag-specialuse.Plo
+ -rm -f ./$(DEPDIR)/tst-specialuse-exists.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.c b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.c
new file mode 100644
index 0000000..fcaf1b5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.c
@@ -0,0 +1,31 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "imap-arg.h"
+
+#include "ext-special-use-common.h"
+
+bool ext_special_use_flag_valid(const char *flag)
+{
+ const char *p = flag;
+
+ /* RFC 6154, Section 6:
+
+ use-attr = "\All" / "\Archive" / "\Drafts" / "\Flagged" /
+ "\Junk" / "\Sent" / "\Trash" / use-attr-ext
+ use-attr-ext = "\" atom
+ */
+
+ /* "\" */
+ if (*p != '\\')
+ return FALSE;
+ p++;
+
+ /* atom */
+ for (; *p != '\0'; p++) {
+ if (!IS_ATOM_CHAR(*p))
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.h b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.h
new file mode 100644
index 0000000..61f23d0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use-common.h
@@ -0,0 +1,43 @@
+#ifndef EXT_SPECIAL_USE_COMMON_H
+#define EXT_SPECIAL_USE_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Tagged arguments
+ */
+
+extern const struct sieve_argument_def specialuse_tag;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def specialuse_exists_test;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def specialuse_operand;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def specialuse_exists_operation;
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def special_use_extension;
+
+/*
+ * Flag checking
+ */
+
+bool ext_special_use_flag_valid(const char *flag);
+
+#endif /* EXT_SPECIAL_USE_COMMON_H */
+
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use.c b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use.c
new file mode 100644
index 0000000..acbb13e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/ext-special-use.c
@@ -0,0 +1,57 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+/* Extension special-use
+ * ---------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 8579
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * Extension
+ */
+
+static bool
+ext_special_use_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+
+const struct sieve_extension_def special_use_extension = {
+ .name = "special-use",
+ .validator_load = ext_special_use_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(specialuse_exists_operation),
+ SIEVE_EXT_DEFINE_OPERAND(specialuse_operand)
+};
+
+static bool
+ext_special_use_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register :specialuse tag with fileinto command and we don't care
+ whether this command is registered or even whether it will be
+ registered at all. The validator handles either situation gracefully.
+ */
+ sieve_validator_register_external_tag(
+ valdtr, "fileinto", ext, &specialuse_tag,
+ SIEVE_OPT_SIDE_EFFECT);
+
+ /* Register new test */
+ sieve_validator_register_command(valdtr, ext, &specialuse_exists_test);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/tag-specialuse.c b/pigeonhole/src/lib-sieve/plugins/special-use/tag-specialuse.c
new file mode 100644
index 0000000..0f6d32a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/tag-specialuse.c
@@ -0,0 +1,316 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * Flags tagged argument
+ */
+
+static bool
+tag_specialuse_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+tag_specialuse_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+const struct sieve_argument_def specialuse_tag = {
+ .identifier = "specialuse",
+ .validate = tag_specialuse_validate,
+ .generate = tag_specialuse_generate
+};
+
+/*
+ * Side effect
+ */
+
+static bool
+seff_specialuse_dump_context(const struct sieve_side_effect *seffect,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+seff_specialuse_read_context(const struct sieve_side_effect *seffect,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **context);
+
+static int
+seff_specialuse_merge(const struct sieve_runtime_env *renv,
+ const struct sieve_action *action,
+ const struct sieve_side_effect *old_seffect,
+ const struct sieve_side_effect *new_seffect,
+ void **old_context);
+
+static void
+seff_specialuse_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+seff_specialuse_pre_execute(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void **se_tr_context ATTR_UNUSED);
+
+const struct sieve_side_effect_def specialuse_side_effect = {
+ SIEVE_OBJECT("specialuse", &specialuse_operand, 0),
+ .precedence = 200,
+ .to_action = &act_store,
+ .dump_context = seff_specialuse_dump_context,
+ .read_context = seff_specialuse_read_context,
+ .merge = seff_specialuse_merge,
+ .print = seff_specialuse_print,
+ .pre_execute = seff_specialuse_pre_execute
+};
+
+/*
+ * Operand
+ */
+
+static const struct sieve_extension_objects ext_side_effects =
+ SIEVE_EXT_DEFINE_SIDE_EFFECT(specialuse_side_effect);
+
+const struct sieve_operand_def specialuse_operand = {
+ .name = "specialuse operand",
+ .ext_def = &special_use_extension,
+ .class = &sieve_side_effect_operand_class,
+ .interface = &ext_side_effects
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+tag_specialuse_validate(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* Check syntax:
+ * :specialuse <special-use-flag: string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+ if (sieve_argument_is_string_literal(*arg)) {
+ const char *use_flag = sieve_ast_argument_strc(*arg);
+
+ if (!ext_special_use_flag_valid(use_flag)) {
+ sieve_argument_validate_error(
+ valdtr, *arg, "specialuse tag: "
+ "invalid special-use flag `%s' specified",
+ str_sanitize(use_flag, 64));
+ return FALSE;
+ }
+ }
+
+ tag->parameters = *arg;
+
+ /* Detach parameter */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+tag_specialuse_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *param;
+
+ if (sieve_ast_argument_type(arg) != SAAT_TAG)
+ return FALSE;
+
+ sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
+ &specialuse_side_effect);
+
+ /* Explicit :specialuse tag */
+ param = arg->parameters;
+
+ /* Call the generation function for the argument */
+ if (param->argument != NULL && param->argument->def != NULL &&
+ param->argument->def->generate != NULL &&
+ !param->argument->def->generate(cgenv, param, cmd))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Side effect implementation
+ */
+
+/* Context data */
+
+struct seff_specialuse_context {
+ const char *special_use_flag;
+};
+
+/* Context coding */
+
+static bool
+seff_specialuse_dump_context(
+ const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ return sieve_opr_stringlist_dump(denv, address, "specialuse");
+}
+
+static int
+seff_specialuse_read_context(
+ const struct sieve_side_effect *seffect ATTR_UNUSED,
+ const struct sieve_runtime_env *renv, sieve_size_t *address,
+ void **se_context)
+{
+ pool_t pool = sieve_result_pool(renv->result);
+ struct seff_specialuse_context *ctx;
+ string_t *special_use_flag;
+ const char *use_flag;
+ int ret;
+
+ if ((ret = sieve_opr_string_read(renv, address, "specialuse",
+ &special_use_flag)) <= 0)
+ return ret;
+
+ use_flag = str_c(special_use_flag);
+ if (!ext_special_use_flag_valid(use_flag)) {
+ sieve_runtime_error(
+ renv, NULL, "specialuse tag: "
+ "invalid special-use flag `%s' specified",
+ str_sanitize(use_flag, 64));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ ctx = p_new(pool, struct seff_specialuse_context, 1);
+ ctx->special_use_flag = p_strdup(pool, use_flag);
+
+ *se_context = (void *) ctx;
+
+ return SIEVE_EXEC_OK;
+}
+
+/* Result verification */
+
+static int
+seff_specialuse_merge(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_side_effect *old_seffect ATTR_UNUSED,
+ const struct sieve_side_effect *new_seffect,
+ void **old_context)
+{
+ if (new_seffect != NULL)
+ *old_context = new_seffect->context;
+
+ return 1;
+}
+
+/* Result printing */
+
+static void
+seff_specialuse_print(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ struct seff_specialuse_context *ctx =
+ (struct seff_specialuse_context *)seffect->context;
+
+ sieve_result_seffect_printf(
+ rpenv,
+ "use mailbox with special-use flag `%s' instead if accessible",
+ ctx->special_use_flag);
+}
+
+/* Result execution */
+
+static int
+seff_specialuse_pre_execute(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void **se_tr_context ATTR_UNUSED)
+{
+ struct seff_specialuse_context *ctx =
+ (struct seff_specialuse_context *)seffect->context;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+ struct mailbox *box;
+
+ if (trans->box == NULL || trans->disabled)
+ return SIEVE_EXEC_OK;
+
+ /* Check whether something already failed */
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ break;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ trans->error = NULL;
+ trans->error_code = MAIL_ERROR_NONE;
+
+ box = mailbox_alloc_for_user(eenv->scriptenv->user,
+ ctx->special_use_flag,
+ (MAILBOX_FLAG_POST_SESSION |
+ MAILBOX_FLAG_SPECIAL_USE));
+
+ /* We still override the allocate default mailbox with ours below even
+ when the default and special-use mailbox are identical. Choosing
+ either one is (currently) equal and setting trans->mailbox_identifier
+ for SPECIAL-USE needs to be done either way, so we use the same code
+ path. */
+
+ /* Try to open the mailbox */
+ eenv->exec_status->last_storage = mailbox_get_storage(box);
+ if (mailbox_open(box) == 0) {
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ /* Success */
+ mailbox_free(&trans->box);
+ trans->box = box;
+ trans->mailbox_identifier = p_strdup_printf(pool,
+ "[SPECIAL-USE %s]", ctx->special_use_flag);
+
+ } else {
+ /* Failure */
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
+ /* Not found; revert to default */
+ mailbox_free(&box);
+ } else {
+ /* Total failure */
+ mailbox_free(&trans->box);
+ trans->box = box;
+ sieve_act_store_get_storage_error(aenv, trans);
+ return (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ }
+ }
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c b/pigeonhole/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c
new file mode 100644
index 0000000..a1aa878
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/special-use/tst-specialuse-exists.c
@@ -0,0 +1,525 @@
+/* Copyright (c) 2019 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+
+#include "sieve-common.h"
+#include "sieve-actions.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-special-use-common.h"
+
+/*
+ * specialuse_exists command
+ *
+ * Syntax:
+ * specialuse_exists [<mailbox: string>]
+ * <special-use-flags: string-list>
+ */
+
+static bool
+tst_specialuse_exists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def specialuse_exists_test = {
+ .identifier = "specialuse_exists",
+ .type = SCT_TEST,
+ .positional_args = -1, /* We check positional arguments ourselves */
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_specialuse_exists_validate,
+ .generate = tst_specialuse_exists_generate,
+};
+
+/*
+ * Mailboxexists operation
+ */
+
+static bool
+tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def specialuse_exists_operation = {
+ .mnemonic = "SPECIALUSE_EXISTS",
+ .ext_def = &special_use_extension,
+ .dump = tst_specialuse_exists_operation_dump,
+ .execute = tst_specialuse_exists_operation_execute,
+};
+
+/*
+ * Test validation
+ */
+
+struct _validate_context {
+ struct sieve_validator *valdtr;
+ struct sieve_command *tst;
+};
+
+static int
+tst_specialuse_exists_flag_validate(void *context,
+ struct sieve_ast_argument *arg)
+{
+ struct _validate_context *valctx = (struct _validate_context *)context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *flag = sieve_ast_argument_strc(arg);
+
+ if (!ext_special_use_flag_valid(flag)) {
+ sieve_argument_validate_error(
+ valctx->valdtr, arg, "%s test: "
+ "invalid special-use flag `%s' specified",
+ sieve_command_identifier(valctx->tst),
+ str_sanitize(flag, 64));
+ }
+ }
+
+ return 1;
+}
+
+static bool
+tst_specialuse_exists_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *arg2;
+ struct sieve_ast_argument *aarg;
+ struct _validate_context valctx;
+
+ if (arg == NULL) {
+ sieve_command_validate_error(
+ valdtr, tst, "the %s %s expects at least one argument, "
+ "but none was found",
+ sieve_command_identifier(tst),
+ sieve_command_type_name(tst));
+ return FALSE;
+ }
+
+ if (sieve_ast_argument_type(arg) != SAAT_STRING &&
+ sieve_ast_argument_type(arg) != SAAT_STRING_LIST) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "the %s %s expects either a string (mailbox) or "
+ "a string-list (special-use flags) as first argument, "
+ "but %s was found",
+ sieve_command_identifier(tst),
+ sieve_command_type_name(tst),
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ arg2 = sieve_ast_argument_next(arg);
+ if (arg2 != NULL) {
+ /* First, check syntax sanity */
+ if (sieve_ast_argument_type(arg) != SAAT_STRING) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "if a second argument is specified for the %s %s, "
+ "the first must be a string (mailbox), "
+ "but %s was found",
+ sieve_command_identifier(tst),
+ sieve_command_type_name(tst),
+ sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+ if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
+ return FALSE;
+
+ /* Check name validity when mailbox argument is not a variable */
+ if (sieve_argument_is_string_literal(arg)) {
+ const char *mailbox = sieve_ast_argument_strc(arg);
+ const char *error;
+
+ if (!sieve_mailbox_check_name(mailbox, &error)) {
+ sieve_argument_validate_warning(
+ valdtr, arg, "%s test: "
+ "invalid mailbox name `%s' specified: %s",
+ sieve_command_identifier(tst),
+ str_sanitize(mailbox, 256), error);
+ }
+ }
+
+ if (sieve_ast_argument_type(arg2) != SAAT_STRING &&
+ sieve_ast_argument_type(arg2) != SAAT_STRING_LIST) {
+ sieve_argument_validate_error(
+ valdtr, arg2,
+ "the %s %s expects a string list (special-use flags) as "
+ "second argument when two arguments are specified, "
+ "but %s was found",
+ sieve_command_identifier(tst),
+ sieve_command_type_name(tst),
+ sieve_ast_argument_name(arg2));
+ return FALSE;
+ }
+ } else
+ arg2 = arg;
+
+ if (!sieve_validator_argument_activate(valdtr, tst, arg2, FALSE))
+ return FALSE;
+
+ aarg = arg2;
+ memset(&valctx, 0, sizeof(valctx));
+ valctx.valdtr = valdtr;
+ valctx.tst = tst;
+
+ return (sieve_ast_stringlist_map(
+ &aarg, (void*)&valctx,
+ tst_specialuse_exists_flag_validate) >= 0);
+}
+
+/*
+ * Test generation
+ */
+
+static bool
+tst_specialuse_exists_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *arg2;
+
+ sieve_operation_emit(cgenv->sblock,
+ tst->ext, &specialuse_exists_operation);
+
+ /* Generate arguments */
+ arg2 = sieve_ast_argument_next(arg);
+ if (arg2 != NULL) {
+ if (!sieve_generate_argument(cgenv, arg, tst))
+ return FALSE;
+ } else {
+ sieve_opr_omitted_emit(cgenv->sblock);
+ arg2 = arg;
+ }
+ return sieve_generate_argument(cgenv, arg2, tst);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_specialuse_exists_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ struct sieve_operand oprnd;
+
+ sieve_code_dumpf(denv, "SPECIALUSE_EXISTS");
+ sieve_code_descend(denv);
+
+ sieve_code_mark(denv);
+ if (!sieve_operand_read(denv->sblock, address, NULL, &oprnd)) {
+ sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+ return FALSE;
+ }
+
+ if (!sieve_operand_is_omitted(&oprnd)) {
+ return (sieve_opr_string_dump_data(denv, &oprnd,
+ address, "mailbox") &&
+ sieve_opr_stringlist_dump(denv, address,
+ "special-use-flags"));
+ }
+
+ return sieve_opr_stringlist_dump(denv, address, "special-use-flags");
+}
+
+/*
+ * Code execution
+ */
+
+static int
+tst_specialuse_find_mailbox(const struct sieve_runtime_env *renv,
+ const char *mailbox, struct mailbox **box_r)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct mail_user *user = eenv->scriptenv->user;
+ struct mailbox *box;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ enum mail_error error_code;
+ const char *error;
+
+ *box_r = NULL;
+
+ if (user == NULL)
+ return 0;
+
+ /* Open the box */
+ box = mailbox_alloc_for_user(user, mailbox, MAILBOX_FLAG_POST_SESSION);
+ if (mailbox_open(box) < 0) {
+ error = mailbox_get_last_internal_error(box, &error_code);
+
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox `%s' cannot be opened: %s",
+ str_sanitize(mailbox, 256), error);
+ }
+
+ mailbox_free(&box);
+
+ if (error_code == MAIL_ERROR_TEMP) {
+ sieve_runtime_error(
+ renv, NULL, "specialuse_exists test: "
+ "failed to open mailbox `%s': %s",
+ str_sanitize(mailbox, 256), error);
+ return -1;
+ }
+ return 0;
+ }
+
+ /* Also fail when it is readonly */
+ if (mailbox_is_readonly(box)) {
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox `%s' is read-only",
+ str_sanitize(mailbox, 256));
+ }
+
+ mailbox_free(&box);
+ return 0;
+ }
+
+ *box_r = box;
+ return 1;
+}
+
+static int
+tst_specialuse_find_specialuse(const struct sieve_runtime_env *renv,
+ const char *special_use)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct mail_user *user = eenv->scriptenv->user;
+ struct mailbox *box;
+ bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ enum mail_error error_code;
+ const char *error;
+
+ if (user == NULL)
+ return 0;
+
+ /* Open the box */
+ box = mailbox_alloc_for_user(user, special_use,
+ (MAILBOX_FLAG_POST_SESSION |
+ MAILBOX_FLAG_SPECIAL_USE));
+ if (mailbox_open(box) < 0) {
+ error = mailbox_get_last_internal_error(box, &error_code);
+
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox with special-use flag `%s' "
+ "cannot be opened: %s",
+ str_sanitize(special_use, 64), error);
+ }
+
+ mailbox_free(&box);
+
+ if (error_code == MAIL_ERROR_TEMP) {
+ sieve_runtime_error(
+ renv, NULL, "specialuse_exists test: "
+ "failed to open mailbox with special-use flag`%s': %s",
+ str_sanitize(special_use, 64), error);
+ return -1;
+ }
+ return 0;
+ }
+
+ /* Also fail when it is readonly */
+ if (mailbox_is_readonly(box)) {
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0,
+ "mailbox with special-use flag `%s' is read-only",
+ str_sanitize(special_use, 64));
+ }
+
+ mailbox_free(&box);
+ return 0;
+ }
+
+ mailbox_free(&box);
+ return 1;
+}
+
+static int
+tst_specialuse_exists_check_flag(const struct sieve_runtime_env *renv,
+ struct mailbox *box, const char *use_flag,
+ bool trace, bool *all_exist_r)
+{
+ int ret;
+
+ if (!ext_special_use_flag_valid(use_flag)) {
+ sieve_runtime_error(
+ renv, NULL, "specialuse_exists test: "
+ "invalid special-use flag `%s' specified",
+ str_sanitize(use_flag, 64));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if (box != NULL) {
+ /* Mailbox has this SPECIAL-USE flag? */
+ if (!mailbox_has_special_use(box, use_flag)) {
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+ } else {
+ /* Is there mailbox with this SPECIAL-USE flag? */
+ ret = tst_specialuse_find_specialuse(renv, use_flag);
+ if (ret < 0)
+ return SIEVE_EXEC_TEMP_FAILURE;
+ if (ret == 0) {
+ *all_exist_r = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+ }
+
+ if (trace) {
+ sieve_runtime_trace(
+ renv, 0, "special-use flag `%s' exists",
+ str_sanitize(use_flag, 80));
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+tst_specialuse_exists_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ struct sieve_operand oprnd;
+ struct sieve_stringlist *special_use_flags;
+ string_t *mailbox, *special_use_flag;
+ struct mailbox *box = NULL;
+ const char *error;
+ bool trace = FALSE, all_exist = TRUE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read bare operand (two types possible) */
+ ret = sieve_operand_runtime_read(renv, address, NULL, &oprnd);
+ if (ret <= 0)
+ return ret;
+
+ /* Mailbox operand (optional) */
+ mailbox = NULL;
+ if (!sieve_operand_is_omitted(&oprnd)) {
+ /* Read the mailbox operand */
+ ret = sieve_opr_string_read_data(renv, &oprnd, address,
+ "mailbox", &mailbox);
+ if (ret <= 0)
+ return ret;
+
+ /* Read flag list */
+ ret = sieve_opr_stringlist_read(renv, address,
+ "special-use-flags",
+ &special_use_flags);
+ if (ret <= 0)
+ return ret;
+
+ /* Flag-list operand */
+ } else {
+ /* Read flag list */
+ ret = sieve_opr_stringlist_read(renv, address,
+ "special-use-flags",
+ &special_use_flags);
+ if (ret <= 0)
+ return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+ sieve_runtime_trace(renv, 0, "specialuse_exists test");
+ sieve_runtime_trace_descend(renv);
+
+ trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+ }
+
+ if (mailbox != NULL) {
+ if (!sieve_mailbox_check_name(str_c(mailbox), &error)) {
+ sieve_runtime_warning(
+ renv, NULL, "specialuse_exists test: "
+ "invalid mailbox name `%s' specified: %s",
+ str_sanitize(str_c(mailbox), 256), error);
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (tst_specialuse_find_mailbox(renv, str_c(mailbox), &box) < 0)
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+
+ if (box == NULL && mailbox != NULL) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox `%s' is not accessible",
+ str_sanitize(str_c(mailbox), 80));
+ sieve_interpreter_set_test_result(renv->interp, FALSE);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (mailbox != NULL) {
+ sieve_runtime_trace(
+ renv, 0, "mailbox `%s' is accessible",
+ str_sanitize(str_c(mailbox), 80));
+ }
+
+ ret = 0;
+ special_use_flag = NULL;
+ while (all_exist &&
+ (ret = sieve_stringlist_next_item(
+ special_use_flags, &special_use_flag)) > 0) {
+ const char *use_flag = str_c(special_use_flag);
+
+ ret = tst_specialuse_exists_check_flag(
+ renv, box, use_flag, trace, &all_exist);
+ if (ret <= 0) {
+ if (box != NULL) {
+ /* Close mailbox */
+ mailbox_free(&box);
+ }
+ return ret;
+ }
+ }
+
+ if (box != NULL) {
+ /* Close mailbox */
+ mailbox_free(&box);
+ }
+
+ if (ret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid special-use flag item");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (trace) {
+ if (all_exist) {
+ sieve_runtime_trace(
+ renv, 0, "all special-use flags are set");
+ } else {
+ sieve_runtime_trace(
+ renv, 0, "some special-use are not set");
+ }
+ }
+
+ sieve_interpreter_set_test_result(renv->interp, all_exist);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.am b/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.am
new file mode 100644
index 0000000..2bab53f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libsieve_ext_subaddress.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_subaddress_la_SOURCES = \
+ ext-subaddress.c
diff --git a/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in b/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in
new file mode 100644
index 0000000..c5b6b7c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in
@@ -0,0 +1,673 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/subaddress
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_subaddress_la_LIBADD =
+am_libsieve_ext_subaddress_la_OBJECTS = ext-subaddress.lo
+libsieve_ext_subaddress_la_OBJECTS = \
+ $(am_libsieve_ext_subaddress_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-subaddress.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_subaddress_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_subaddress_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_subaddress.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_subaddress_la_SOURCES = \
+ ext-subaddress.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/subaddress/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/subaddress/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_subaddress.la: $(libsieve_ext_subaddress_la_OBJECTS) $(libsieve_ext_subaddress_la_DEPENDENCIES) $(EXTRA_libsieve_ext_subaddress_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_subaddress_la_OBJECTS) $(libsieve_ext_subaddress_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-subaddress.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-subaddress.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-subaddress.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/subaddress/ext-subaddress.c b/pigeonhole/src/lib-sieve/plugins/subaddress/ext-subaddress.c
new file mode 100644
index 0000000..abaa7ae
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/subaddress/ext-subaddress.c
@@ -0,0 +1,191 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension subaddress
+ * --------------------
+ *
+ * Author: Stephan Bosch
+ * Specification: RFC 3598
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-settings.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+
+#include <string.h>
+
+/*
+ * Configuration
+ */
+
+#define SUBADDRESS_DEFAULT_DELIM "+"
+
+struct ext_subaddress_config {
+ char *delimiter;
+};
+
+/*
+ * Forward declarations
+ */
+
+const struct sieve_address_part_def user_address_part;
+const struct sieve_address_part_def detail_address_part;
+
+static struct sieve_operand_def subaddress_operand;
+
+/*
+ * Extension
+ */
+
+static bool ext_subaddress_load
+ (const struct sieve_extension *ext, void **context);
+static void ext_subaddress_unload
+ (const struct sieve_extension *ext);
+static bool ext_subaddress_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def subaddress_extension = {
+ .name = "subaddress",
+ .load = ext_subaddress_load,
+ .unload = ext_subaddress_unload,
+ .validator_load = ext_subaddress_validator_load,
+ SIEVE_EXT_DEFINE_OPERAND(subaddress_operand)
+};
+
+static bool ext_subaddress_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct ext_subaddress_config *config;
+ const char *delim;
+
+ if ( *context != NULL ) {
+ ext_subaddress_unload(ext);
+ }
+
+ delim = sieve_setting_get(ext->svinst, "recipient_delimiter");
+
+ if ( delim == NULL )
+ delim = SUBADDRESS_DEFAULT_DELIM;
+
+ config = i_new(struct ext_subaddress_config, 1);
+ config->delimiter = i_strdup(delim);
+
+ *context = (void *) config;
+
+ return TRUE;
+}
+
+static void ext_subaddress_unload
+(const struct sieve_extension *ext)
+{
+ struct ext_subaddress_config *config =
+ (struct ext_subaddress_config *) ext->context;
+
+ i_free(config->delimiter);
+ i_free(config);
+}
+
+static bool ext_subaddress_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ sieve_address_part_register(validator, ext, &user_address_part);
+ sieve_address_part_register(validator, ext, &detail_address_part);
+
+ return TRUE;
+}
+
+/*
+ * Address parts
+ */
+
+enum ext_subaddress_address_part {
+ SUBADDRESS_USER,
+ SUBADDRESS_DETAIL
+};
+
+/* Forward declarations */
+
+static const char *subaddress_user_extract_from
+ (const struct sieve_address_part *addrp, const struct smtp_address *address);
+static const char *subaddress_detail_extract_from
+ (const struct sieve_address_part *addrp, const struct smtp_address *address);
+
+/* Address part objects */
+
+const struct sieve_address_part_def user_address_part = {
+ SIEVE_OBJECT("user",
+ &subaddress_operand, SUBADDRESS_USER),
+ subaddress_user_extract_from
+};
+
+const struct sieve_address_part_def detail_address_part = {
+ SIEVE_OBJECT("detail",
+ &subaddress_operand, SUBADDRESS_DETAIL),
+ .extract_from = subaddress_detail_extract_from
+};
+
+/* Address part implementation */
+
+static const char *subaddress_user_extract_from
+(const struct sieve_address_part *addrp, const struct smtp_address *address)
+{
+ struct ext_subaddress_config *config =
+ (struct ext_subaddress_config *) addrp->object.ext->context;
+ const char *delim;
+ size_t idx;
+
+ idx = strcspn(address->localpart, config->delimiter);
+ delim = address->localpart[idx] != '\0' ? address->localpart + idx : NULL;
+
+ if ( delim == NULL ) return address->localpart;
+
+ return t_strdup_until(address->localpart, delim);
+}
+
+static const char *subaddress_detail_extract_from
+(const struct sieve_address_part *addrp, const struct smtp_address *address)
+{
+ struct ext_subaddress_config *config =
+ (struct ext_subaddress_config *) addrp->object.ext->context;
+ const char *delim;
+ size_t idx;
+
+ idx = strcspn(address->localpart, config->delimiter);
+ delim = address->localpart[idx] != '\0' ? address->localpart + idx + 1: NULL;
+
+ /* Just to be sure */
+ if ( delim == NULL ||
+ delim > (address->localpart + strlen(address->localpart)) )
+ return NULL;
+ return delim;
+}
+
+/*
+ * Operand
+ */
+
+const struct sieve_address_part_def *ext_subaddress_parts[] = {
+ &user_address_part, &detail_address_part
+};
+
+static const struct sieve_extension_objects ext_address_parts =
+ SIEVE_EXT_DEFINE_ADDRESS_PARTS(ext_subaddress_parts);
+
+static struct sieve_operand_def subaddress_operand = {
+ .name = "address-part",
+ .ext_def = &subaddress_extension,
+ .class = &sieve_address_part_operand_class,
+ .interface = &ext_address_parts
+};
+
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.am b/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.am
new file mode 100644
index 0000000..09df27b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_ext_vacation.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-vacation.c
+
+libsieve_ext_vacation_la_SOURCES = \
+ $(cmds) \
+ ext-vacation-common.c \
+ ext-vacation.c \
+ ext-vacation-seconds.c
+
+noinst_HEADERS = \
+ ext-vacation-common.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in b/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in
new file mode 100644
index 0000000..f18806a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in
@@ -0,0 +1,700 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/vacation
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_vacation_la_LIBADD =
+am__objects_1 = cmd-vacation.lo
+am_libsieve_ext_vacation_la_OBJECTS = $(am__objects_1) \
+ ext-vacation-common.lo ext-vacation.lo ext-vacation-seconds.lo
+libsieve_ext_vacation_la_OBJECTS = \
+ $(am_libsieve_ext_vacation_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-vacation.Plo \
+ ./$(DEPDIR)/ext-vacation-common.Plo \
+ ./$(DEPDIR)/ext-vacation-seconds.Plo \
+ ./$(DEPDIR)/ext-vacation.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_vacation_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_vacation_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_vacation.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ -I$(srcdir)/../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-vacation.c
+
+libsieve_ext_vacation_la_SOURCES = \
+ $(cmds) \
+ ext-vacation-common.c \
+ ext-vacation.c \
+ ext-vacation-seconds.c
+
+noinst_HEADERS = \
+ ext-vacation-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/vacation/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/vacation/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_vacation.la: $(libsieve_ext_vacation_la_OBJECTS) $(libsieve_ext_vacation_la_DEPENDENCIES) $(EXTRA_libsieve_ext_vacation_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_vacation_la_OBJECTS) $(libsieve_ext_vacation_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-vacation.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vacation-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vacation-seconds.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vacation.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-vacation.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation-common.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation-seconds.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-vacation.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation-common.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation-seconds.Plo
+ -rm -f ./$(DEPDIR)/ext-vacation.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c b/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c
new file mode 100644
index 0000000..ddc14be
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -0,0 +1,1578 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "md5.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-address.h"
+#include "message-date.h"
+#include "var-expand.h"
+#include "ioloop.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-vacation-common.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static const struct sieve_argument_def vacation_days_tag;
+static const struct sieve_argument_def vacation_subject_tag;
+static const struct sieve_argument_def vacation_from_tag;
+static const struct sieve_argument_def vacation_addresses_tag;
+static const struct sieve_argument_def vacation_mime_tag;
+static const struct sieve_argument_def vacation_handle_tag;
+
+/*
+ * Vacation command
+ *
+ * Syntax:
+ * vacation [":days" number] [":subject" string]
+ * [":from" string] [":addresses" string-list]
+ * [":mime"] [":handle" string] <reason: string>
+ */
+
+static bool
+cmd_vacation_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_vacation_pre_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_vacation_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_vacation_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+const struct sieve_command_def vacation_command = {
+ .identifier = "vacation",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_vacation_registered,
+ .pre_validate = cmd_vacation_pre_validate,
+ .validate = cmd_vacation_validate,
+ .generate = cmd_vacation_generate,
+};
+
+/*
+ * Vacation command tags
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_vacation_validate_number_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_vacation_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def vacation_days_tag = {
+ .identifier = "days",
+ .validate = cmd_vacation_validate_number_tag
+};
+
+static const struct sieve_argument_def vacation_seconds_tag = {
+ .identifier = "seconds",
+ .validate = cmd_vacation_validate_number_tag
+};
+
+static const struct sieve_argument_def vacation_subject_tag = {
+ .identifier = "subject",
+ .validate = cmd_vacation_validate_string_tag
+};
+
+static const struct sieve_argument_def vacation_from_tag = {
+ .identifier = "from",
+ .validate = cmd_vacation_validate_string_tag
+};
+
+static const struct sieve_argument_def vacation_addresses_tag = {
+ .identifier = "addresses",
+ .validate = cmd_vacation_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def vacation_mime_tag = {
+ .identifier = "mime",
+ .validate = cmd_vacation_validate_mime_tag
+};
+
+static const struct sieve_argument_def vacation_handle_tag = {
+ .identifier = "handle",
+ .validate = cmd_vacation_validate_string_tag
+};
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+ OPT_END,
+ OPT_SECONDS,
+ OPT_SUBJECT,
+ OPT_FROM,
+ OPT_ADDRESSES,
+ OPT_MIME
+};
+
+/*
+ * Vacation operation
+ */
+
+static bool
+ext_vacation_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+ext_vacation_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def vacation_operation = {
+ .mnemonic = "VACATION",
+ .ext_def = &vacation_extension,
+ .dump = ext_vacation_operation_dump,
+ .execute = ext_vacation_operation_execute
+};
+
+/*
+ * Vacation action
+ */
+
+/* Forward declarations */
+
+static int
+act_vacation_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+int act_vacation_check_conflict(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_vacation_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_vacation_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+
+/* Action object */
+
+const struct sieve_action_def act_vacation = {
+ .name = "vacation",
+ .flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
+ .check_duplicate = act_vacation_check_duplicate,
+ .check_conflict = act_vacation_check_conflict,
+ .print = act_vacation_print,
+ .commit = act_vacation_commit
+};
+
+/* Action context information */
+
+struct act_vacation_context {
+ const char *reason;
+
+ sieve_number_t seconds;
+ const char *subject;
+ const char *handle;
+ bool mime;
+ const char *from;
+ const struct smtp_address *from_address;
+ const struct smtp_address *const *addresses;
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_vacation_context_data {
+ string_t *from;
+ string_t *subject;
+
+ bool mime;
+
+ struct sieve_ast_argument *handle_arg;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+cmd_vacation_validate_number_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *ext = sieve_argument_ext(*arg);
+ const struct ext_vacation_config *config =
+ (const struct ext_vacation_config *)ext->context;
+ struct sieve_ast_argument *tag = *arg;
+ sieve_number_t period, seconds;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :days number
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_NUMBER, FALSE))
+ return FALSE;
+
+ period = sieve_ast_argument_number(*arg);
+ if (sieve_argument_is(tag, vacation_days_tag))
+ seconds = period * (24*60*60);
+ else if (sieve_argument_is(tag, vacation_seconds_tag))
+ seconds = period;
+ else
+ i_unreached();
+
+ /* Enforce :seconds >= min_period */
+ if (seconds < config->min_period) {
+ seconds = config->min_period;
+
+ sieve_argument_validate_warning(
+ valdtr, *arg,
+ "specified :%s value '%llu' is under the minimum",
+ sieve_argument_identifier(tag),
+ (unsigned long long)period);
+ /* Enforce :days <= max_period */
+ } else if (config->max_period > 0 && seconds > config->max_period) {
+ seconds = config->max_period;
+
+ sieve_argument_validate_warning(
+ valdtr, *arg,
+ "specified :%s value '%llu' is over the maximum",
+ sieve_argument_identifier(tag),
+ (unsigned long long)period);
+ }
+
+ sieve_ast_argument_number_set(*arg, seconds);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool
+cmd_vacation_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_vacation_context_data *ctx_data =
+ (struct cmd_vacation_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :subject string
+ * :from string
+ * :handle string
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is(tag, vacation_from_tag)) {
+ if (sieve_argument_is_string_literal(*arg)) {
+ string_t *address = sieve_ast_argument_str(*arg);
+ const char *error;
+ bool result;
+
+ T_BEGIN {
+ result = sieve_address_validate_str(address,
+ &error);
+
+ if (!result) {
+ sieve_argument_validate_error(
+ valdtr, *arg,
+ "specified :from address '%s' is invalid for vacation action: %s",
+ str_sanitize(str_c(address), 128),
+ error);
+ }
+ } T_END;
+
+ if (!result)
+ return FALSE;
+ }
+
+ ctx_data->from = sieve_ast_argument_str(*arg);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ } else if (sieve_argument_is(tag, vacation_subject_tag)) {
+ ctx_data->subject = sieve_ast_argument_str(*arg);
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ } else if (sieve_argument_is(tag, vacation_handle_tag)) {
+ ctx_data->handle_arg = *arg;
+
+ /* Detach optional argument (emitted as mandatory) */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+ }
+ return TRUE;
+}
+
+static bool
+cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :addresses string-list
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING_LIST, FALSE))
+ return FALSE;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool
+cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_vacation_context_data *ctx_data =
+ (struct cmd_vacation_context_data *)cmd->data;
+
+ ctx_data->mime = TRUE;
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_vacation_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_days_tag, OPT_SECONDS);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_subject_tag, OPT_SUBJECT);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_from_tag, OPT_FROM);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_addresses_tag, OPT_ADDRESSES);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_mime_tag, OPT_MIME);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &vacation_handle_tag, 0);
+ return TRUE;
+}
+
+bool ext_vacation_register_seconds_tag(
+ struct sieve_validator *valdtr,
+ const struct sieve_extension *vacation_ext)
+{
+ sieve_validator_register_external_tag(
+ valdtr, vacation_command.identifier, vacation_ext,
+ &vacation_seconds_tag, OPT_SECONDS);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_vacation_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct cmd_vacation_context_data *ctx_data;
+
+ /* Assign context */
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_vacation_context_data, 1);
+ cmd->data = ctx_data;
+
+ return TRUE;
+}
+
+static const char _handle_empty_subject[] = "<default-subject>";
+static const char _handle_empty_from[] = "<default-from>";
+static const char _handle_mime_enabled[] = "<MIME>";
+static const char _handle_mime_disabled[] = "<NO-MIME>";
+
+static bool
+cmd_vacation_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ struct cmd_vacation_context_data *ctx_data =
+ (struct cmd_vacation_context_data *)cmd->data;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason", 1,
+ SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ /* Construct handle if not set explicitly */
+ if (ctx_data->handle_arg == NULL) {
+ T_BEGIN {
+ string_t *handle;
+ string_t *reason = sieve_ast_argument_str(arg);
+ unsigned int size = str_len(reason);
+
+ /* Precalculate the size of it all */
+ size += (ctx_data->subject == NULL ?
+ sizeof(_handle_empty_subject) - 1 :
+ str_len(ctx_data->subject));
+ size += (ctx_data->from == NULL ?
+ sizeof(_handle_empty_from) - 1 :
+ str_len(ctx_data->from));
+ size += (ctx_data->mime ?
+ sizeof(_handle_mime_enabled) - 1 :
+ sizeof(_handle_mime_disabled) - 1);
+
+ /* Construct the string */
+ handle = t_str_new(size);
+ str_append_str(handle, reason);
+
+ if (ctx_data->subject != NULL)
+ str_append_str(handle, ctx_data->subject);
+ else
+ str_append(handle, _handle_empty_subject);
+
+ if (ctx_data->from != NULL)
+ str_append_str(handle, ctx_data->from);
+ else
+ str_append(handle, _handle_empty_from);
+
+ str_append(handle, (ctx_data->mime ?
+ _handle_mime_enabled :
+ _handle_mime_disabled));
+
+ /* Create positional handle argument */
+ ctx_data->handle_arg =
+ sieve_ast_argument_string_create(
+ cmd->ast_node, handle,
+ sieve_ast_node_line(cmd->ast_node));
+ } T_END;
+
+ if (!sieve_validator_argument_activate(
+ valdtr, cmd, ctx_data->handle_arg, TRUE))
+ return FALSE;
+ } else {
+ /* Attach explicit handle argument as positional */
+ (void)sieve_ast_argument_attach(cmd->ast_node,
+ ctx_data->handle_arg);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_vacation_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &vacation_operation);
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+ext_vacation_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "VACATION");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_SECONDS:
+ opok = sieve_opr_number_dump(denv, address, "seconds");
+ break;
+ case OPT_SUBJECT:
+ opok = sieve_opr_string_dump(denv, address, "subject");
+ break;
+ case OPT_FROM:
+ opok = sieve_opr_string_dump(denv, address, "from");
+ break;
+ case OPT_ADDRESSES:
+ opok = sieve_opr_stringlist_dump(denv, address,
+ "addresses");
+ break;
+ case OPT_MIME:
+ sieve_code_dumpf(denv, "mime");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ /* Dump reason and handle operands */
+ return (sieve_opr_string_dump(denv, address, "reason") &&
+ sieve_opr_string_dump(denv, address, "handle"));
+}
+
+/*
+ * Code execution
+ */
+
+static int
+ext_vacation_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ const struct ext_vacation_config *config =
+ (const struct ext_vacation_config *)this_ext->context;
+ struct sieve_side_effects_list *slist = NULL;
+ struct act_vacation_context *act;
+ pool_t pool;
+ int opt_code = 0;
+ sieve_number_t seconds = config->default_period;
+ bool mime = FALSE;
+ struct sieve_stringlist *addresses = NULL;
+ string_t *reason, *subject = NULL, *from = NULL, *handle = NULL;
+ const struct smtp_address *from_address = NULL;
+ int ret;
+
+ /*
+ * Read code
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_SECONDS:
+ ret = sieve_opr_number_read(renv, address, "seconds",
+ &seconds);
+ break;
+ case OPT_SUBJECT:
+ ret = sieve_opr_string_read(renv, address, "subject",
+ &subject);
+ break;
+ case OPT_FROM:
+ ret = sieve_opr_string_read(renv, address, "from",
+ &from);
+ break;
+ case OPT_ADDRESSES:
+ ret = sieve_opr_stringlist_read(renv, address,
+ "addresses",
+ &addresses);
+ break;
+ case OPT_MIME:
+ mime = TRUE;
+ ret = SIEVE_EXEC_OK;
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0)
+ return ret;
+ }
+
+ /* Fixed operands */
+
+ if ((ret =sieve_opr_string_read(renv, address,
+ "reason", &reason)) <= 0 ||
+ (ret = sieve_opr_string_read(renv, address,
+ "handle", &handle)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ /* Trace */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
+ sieve_runtime_trace(renv, 0, "vacation action");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "auto-reply with message `%s'",
+ str_sanitize(str_c(reason), 80));
+ }
+
+ /* Parse :from address */
+ if (from != NULL) {
+ const char *error;
+
+ from_address = sieve_address_parse_str(from, &error);
+ if (from_address == NULL) {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified :from address '%s' is invalid for vacation action: %s",
+ str_sanitize(str_c(from), 128), error);
+ }
+ }
+
+ /* Add vacation action to the result */
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_vacation_context, 1);
+ act->reason = p_strdup(pool, str_c(reason));
+ act->handle = p_strdup(pool, str_c(handle));
+ act->seconds = seconds;
+ act->mime = mime;
+ if (subject != NULL)
+ act->subject = p_strdup(pool, str_c(subject));
+ if (from != NULL) {
+ act->from = p_strdup(pool, str_c(from));
+ act->from_address = smtp_address_clone(pool, from_address);
+ }
+
+ /* Normalize all addresses */
+ if (addresses != NULL) {
+ ARRAY_TYPE(smtp_address_const) addrs;
+ string_t *raw_address;
+ int ret;
+
+ sieve_stringlist_reset(addresses);
+
+ p_array_init(&addrs, pool, 4);
+
+ raw_address = NULL;
+ while ((ret = sieve_stringlist_next_item(addresses,
+ &raw_address)) > 0) {
+ const struct smtp_address *addr;
+ const char *error;
+
+ addr = sieve_address_parse_str(raw_address, &error);
+ if (addr != NULL) {
+ addr = smtp_address_clone(pool, addr);
+ array_append(&addrs, &addr, 1);
+ } else {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified :addresses item '%s' is invalid: "
+ "%s for vacation action (ignored)",
+ str_sanitize(str_c(raw_address),128),
+ error);
+ }
+ }
+
+ if (ret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid addresses stringlist");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ (void)array_append_space(&addrs);
+ act->addresses = array_idx(&addrs, 0);
+ }
+
+ if (sieve_result_add_action(renv, this_ext, "vacation", &act_vacation,
+ slist, (void *)act, 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int
+act_vacation_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ if (!sieve_action_is_executed(act_other, renv->result)) {
+ sieve_runtime_error(
+ renv, act->location,
+ "duplicate vacation action not allowed "
+ "(previously triggered one was here: %s)",
+ act_other->location);
+ return -1;
+ }
+
+ /* Not an error if executed in preceeding script */
+ return 1;
+}
+
+int act_vacation_check_conflict(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ if ((act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0) {
+ if (!sieve_action_is_executed(act_other, renv->result)) {
+ sieve_runtime_error(
+ renv, act->location,
+ "vacation action conflicts with other action: "
+ "the %s action (%s) also sends a response back to the sender",
+ act_other->def->name, act_other->location);
+ return -1;
+ } else {
+ /* Not an error if executed in preceeding script */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Result printing */
+
+static void act_vacation_print(const struct sieve_action *action ATTR_UNUSED,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ struct act_vacation_context *ctx =
+ (struct act_vacation_context *)action->context;
+
+ sieve_result_action_printf(rpenv, "send vacation message:");
+ sieve_result_printf(rpenv, " => seconds : %llu\n",
+ (unsigned long long)ctx->seconds);
+ if (ctx->subject != NULL) {
+ sieve_result_printf(rpenv, " => subject : %s\n",
+ ctx->subject);
+ }
+ if (ctx->from != NULL) {
+ sieve_result_printf(rpenv, " => from : %s\n",
+ ctx->from);
+ }
+ if (ctx->handle != NULL) {
+ sieve_result_printf(rpenv, " => handle : %s\n",
+ ctx->handle);
+ }
+ sieve_result_printf(rpenv, "\nSTART MESSAGE\n%s\nEND MESSAGE\n",
+ ctx->reason);
+}
+
+/* Result execution */
+
+/* Headers known to be associated with mailing lists
+ */
+static const char * const _list_headers[] = {
+ "list-id",
+ "list-owner",
+ "list-subscribe",
+ "list-post",
+ "list-unsubscribe",
+ "list-help",
+ "list-archive",
+ NULL
+};
+
+/* Headers that should be searched for the user's own mail address(es)
+ */
+
+static const char * const _my_address_headers[] = {
+ "to",
+ "cc",
+ "bcc",
+ "resent-to",
+ "resent-cc",
+ "resent-bcc",
+ NULL
+};
+
+/* Headers that should be searched for the full sender address
+ */
+
+static const char * const _sender_headers[] = {
+ "sender",
+ "resent-from",
+ "from",
+ NULL
+};
+
+static inline bool _is_system_address(const struct smtp_address *address)
+{
+ if (strcasecmp(address->localpart, "MAILER-DAEMON") == 0)
+ return TRUE;
+ if (strcasecmp(address->localpart, "LISTSERV") == 0)
+ return TRUE;
+ if (strcasecmp(address->localpart, "majordomo") == 0)
+ return TRUE;
+ if (strstr(address->localpart, "-request") != NULL)
+ return TRUE;
+ if (str_begins(address->localpart, "owner-"))
+ return TRUE;
+ return FALSE;
+}
+
+static bool
+_msg_address_equals(const struct message_address *addr1,
+ const struct smtp_address *addr2)
+{
+ struct smtp_address saddr;
+
+ i_assert(addr1->mailbox != NULL);
+ return (smtp_address_init_from_msg(&saddr, addr1) >= 0 &&
+ smtp_address_equals_icase(addr2, &saddr));
+}
+
+static inline bool
+_header_contains_my_address(const char *header_val,
+ const struct smtp_address *my_address)
+{
+ const struct message_address *msg_addr;
+
+ msg_addr = message_address_parse(pool_datastack_create(),
+ (const unsigned char *)header_val,
+ strlen(header_val), 256, 0);
+ while (msg_addr != NULL) {
+ if (msg_addr->domain != NULL) {
+ if (_msg_address_equals(msg_addr, my_address))
+ return TRUE;
+ }
+
+ msg_addr = msg_addr->next;
+ }
+
+ return FALSE;
+}
+
+static inline bool
+_contains_my_address(const char * const *headers,
+ const struct smtp_address *my_address)
+{
+ const char *const *hdsp = headers;
+
+ while (*hdsp != NULL) {
+ bool result;
+
+ T_BEGIN {
+ result = _header_contains_my_address(*hdsp, my_address);
+ } T_END;
+
+ if (result)
+ return TRUE;
+
+ hdsp++;
+ }
+
+ return FALSE;
+}
+
+static bool _contains_8bit(const char *text)
+{
+ const unsigned char *p = (const unsigned char *)text;
+
+ for (; *p != '\0'; p++) {
+ if ((*p & 0x80) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+_header_get_full_reply_recipient(const struct ext_vacation_config *config,
+ const struct smtp_address *smtp_to,
+ const char *header,
+ struct message_address *reply_to_r)
+{
+ const struct message_address *addr;
+
+ addr = message_address_parse(
+ pool_datastack_create(),
+ (const unsigned char *)header,
+ strlen(header), 256, 0);
+
+ for (; addr != NULL; addr = addr->next) {
+ bool matched = config->to_header_ignore_envelope;
+
+ if (addr->domain == NULL || addr->invalid_syntax)
+ continue;
+
+ if (!matched)
+ matched = _msg_address_equals(addr, smtp_to);
+
+ if (matched) {
+ *reply_to_r = *addr;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static int
+_get_full_reply_recipient(const struct sieve_action_exec_env *aenv,
+ const struct ext_vacation_config *config,
+ const struct smtp_address *smtp_to,
+ struct message_address *reply_to_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ const char *const *hdsp;
+ int ret;
+
+ hdsp = _sender_headers;
+ for (; *hdsp != NULL; hdsp++) {
+ const char *header;
+
+ if ((ret = mail_get_first_header(msgdata->mail, *hdsp,
+ &header)) < 0) {
+ return sieve_result_mail_error(
+ aenv, msgdata->mail,
+ "failed to read header field `%s'", *hdsp);
+ }
+ if (ret == 0 || header == NULL)
+ continue;
+
+ if (_header_get_full_reply_recipient(config, smtp_to,
+ header, reply_to_r))
+ return SIEVE_EXEC_OK;
+ }
+
+ reply_to_r->mailbox = smtp_to->localpart;
+ reply_to_r->domain = smtp_to->domain;
+ return SIEVE_EXEC_OK;
+}
+
+static const struct var_expand_table *
+_get_var_expand_table(const struct sieve_action_exec_env *aenv ATTR_UNUSED,
+ const char *subject)
+{
+ const struct var_expand_table stack_tab[] = {
+ { '$', subject, "subject" },
+ { '\0', NULL, NULL }
+ };
+
+ return p_memdup(unsafe_data_stack_pool, stack_tab, sizeof(stack_tab));
+}
+
+static int
+act_vacation_get_default_subject(const struct sieve_action_exec_env *aenv,
+ const struct ext_vacation_config *config,
+ const char **subject_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ const char *header, *error;
+ string_t *str;
+ const struct var_expand_table *tab;
+ int ret;
+
+ *subject_r = (config->default_subject == NULL ?
+ "Automated reply" : config->default_subject);
+ if ((ret = mail_get_first_header_utf8(msgdata->mail, "subject",
+ &header)) < 0) {
+ return sieve_result_mail_error(
+ aenv, msgdata->mail,
+ "failed to read header field `subject'");
+ }
+ if (ret == 0)
+ return SIEVE_EXEC_OK;
+ if (config->default_subject_template == NULL) {
+ *subject_r = t_strconcat("Auto: ", header, NULL);
+ return SIEVE_EXEC_OK;
+ }
+
+ str = t_str_new(256);
+ tab = _get_var_expand_table(aenv, header);
+ if (var_expand(str, config->default_subject_template,
+ tab, &error) <= 0) {
+ i_error("Failed to expand deliver_log_format=%s: %s",
+ config->default_subject_template, error);
+ *subject_r = t_strconcat("Auto: ", header, NULL);
+ return SIEVE_EXEC_OK;
+ }
+
+ *subject_r = str_c(str);
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_vacation_send(const struct sieve_action_exec_env *aenv,
+ const struct ext_vacation_config *config,
+ struct act_vacation_context *ctx,
+ const struct smtp_address *smtp_to,
+ const struct smtp_address *smtp_from,
+ const struct message_address *reply_from)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct sieve_smtp_context *sctx;
+ struct ostream *output;
+ string_t *msg;
+ struct message_address reply_to;
+ const char *header, *outmsgid, *subject, *error;
+ int ret;
+
+ /* Check smpt functions just to be sure */
+
+ if (!sieve_smtp_available(senv)) {
+ sieve_result_global_warning(
+ aenv, "vacation action has no means to send mail");
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Make sure we have a subject for our reply */
+
+ if (ctx->subject == NULL || *(ctx->subject) == '\0') {
+ if ((ret = act_vacation_get_default_subject(aenv, config,
+ &subject)) <= 0)
+ return ret;
+ } else {
+ subject = ctx->subject;
+ }
+
+ subject = str_sanitize_utf8(subject, config->max_subject_codepoints);
+
+ /* Obtain full To address for reply */
+
+ i_zero(&reply_to);
+ reply_to.mailbox = smtp_to->localpart;
+ reply_to.domain = smtp_to->domain;
+ if ((ret = _get_full_reply_recipient(aenv, config, smtp_to,
+ &reply_to)) <= 0)
+ return ret;
+
+ /* Open smtp session */
+
+ sctx = sieve_smtp_start_single(senv, smtp_to, smtp_from, &output);
+
+ outmsgid = sieve_message_get_new_id(eenv->svinst);
+
+ /* Produce a proper reply */
+
+ msg = t_str_new(512);
+ rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(msg, "Message-ID", outmsgid);
+ rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+ if (ctx->from != NULL && *(ctx->from) != '\0') {
+ rfc2822_header_write_address(msg, "From", ctx->from);
+ } else {
+ if (reply_from == NULL || reply_from->mailbox == NULL ||
+ *reply_from->mailbox == '\0')
+ reply_from = sieve_get_postmaster(senv);
+ rfc2822_header_write(
+ msg, "From",
+ message_address_first_to_string(reply_from));
+ }
+
+ rfc2822_header_write(msg, "To",
+ message_address_first_to_string(&reply_to));
+
+ if (_contains_8bit(subject))
+ rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+ else
+ rfc2822_header_printf(msg, "Subject", "%s", subject);
+
+ /* Compose proper in-reply-to and references headers */
+
+ if ((ret = mail_get_first_header(msgdata->mail, "references",
+ &header)) < 0) {
+ sieve_smtp_abort(sctx);
+ return sieve_result_mail_error(
+ aenv, msgdata->mail,
+ "failed to read header field `references'");
+ }
+
+ if (msgdata->id != NULL) {
+ rfc2822_header_write(msg, "In-Reply-To", msgdata->id);
+
+ if (ret > 0 && header != NULL) {
+ rfc2822_header_write(
+ msg, "References",
+ t_strconcat(header, " ", msgdata->id, NULL));
+ } else {
+ rfc2822_header_write(msg, "References", msgdata->id);
+ }
+ } else if (ret > 0 && header != NULL) {
+ rfc2822_header_write(msg, "References", header);
+ }
+
+ rfc2822_header_write(msg, "Auto-Submitted", "auto-replied (vacation)");
+ rfc2822_header_write(msg, "Precedence", "bulk");
+
+ /* Prevent older Microsoft products from replying to this message */
+ rfc2822_header_write(msg, "X-Auto-Response-Suppress", "All");
+
+ rfc2822_header_write(msg, "MIME-Version", "1.0");
+
+ if (!ctx->mime) {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=utf-8");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+ str_append(msg, "\r\n");
+ }
+
+ str_printfa(msg, "%s\r\n", ctx->reason);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ /* Close smtp session */
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(
+ aenv, "failed to send vacation response to %s: "
+ "<%s> (temporary error)",
+ smtp_address_encode(smtp_to),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(
+ aenv, "failed to send vacation response to %s: "
+ "<%s> (permanent error)",
+ smtp_address_encode(smtp_to),
+ str_sanitize(error, 512));
+ }
+ /* This error will be ignored in the end */
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ eenv->exec_status->significant_action_executed = TRUE;
+ return SIEVE_EXEC_OK;
+}
+
+static void
+act_vacation_hash(struct act_vacation_context *vctx, const char *sender,
+ unsigned char hash_r[])
+{
+ const char *rpath = t_str_lcase(sender);
+ struct md5_context ctx;
+
+ md5_init(&ctx);
+ md5_update(&ctx, rpath, strlen(rpath));
+
+ md5_update(&ctx, vctx->handle, strlen(vctx->handle));
+
+ md5_final(&ctx, hash_r);
+}
+
+static int
+act_vacation_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_extension *ext = action->ext;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ const struct ext_vacation_config *config =
+ (const struct ext_vacation_config *)ext->context;
+ struct act_vacation_context *ctx =
+ (struct act_vacation_context *)action->context;
+ unsigned char dupl_hash[MD5_RESULTLEN];
+ struct mail *mail = sieve_message_get_mail(aenv->msgctx);
+ const struct smtp_address *sender, *recipient;
+ const struct smtp_address *orig_recipient, *user_email;
+ const struct smtp_address *smtp_from;
+ struct message_address reply_from;
+ const char *const *hdsp, *const *headers;
+ int ret;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
+ sieve_result_global_log(
+ aenv, "not sending vacation reply (skipped)");
+ return SIEVE_EXEC_OK;
+ }
+
+ sender = sieve_message_get_sender(aenv->msgctx);
+ recipient = sieve_message_get_final_recipient(aenv->msgctx);
+
+ i_zero(&reply_from);
+ smtp_from = orig_recipient = user_email = NULL;
+
+ /* Is the recipient unset?
+ */
+ if (smtp_address_isnull(recipient)) {
+ sieve_result_global_warning(
+ aenv, "vacation action aborted: "
+ "envelope recipient is <>");
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Is the return path unset ?
+ */
+ if (smtp_address_isnull(sender)) {
+ sieve_result_global_log(aenv, "discarded vacation reply to <>");
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Are we perhaps trying to respond to ourselves ?
+ */
+ if (smtp_address_equals_icase(sender, recipient)) {
+ sieve_result_global_log(
+ aenv, "discarded vacation reply to own address <%s>",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Are we perhaps trying to respond to one of our alternative :addresses?
+ */
+ if (ctx->addresses != NULL) {
+ const struct smtp_address * const *alt_address;
+
+ alt_address = ctx->addresses;
+ while (*alt_address != NULL) {
+ if (smtp_address_equals_icase(sender, *alt_address)) {
+ sieve_result_global_log(
+ aenv,
+ "discarded vacation reply to own address <%s> "
+ "(as specified using :addresses argument)",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+ alt_address++;
+ }
+ }
+
+ /* Did whe respond to this user before? */
+ if (sieve_action_duplicate_check_available(aenv)) {
+ bool duplicate;
+
+ act_vacation_hash(ctx, smtp_address_encode(sender), dupl_hash);
+
+ ret = sieve_action_duplicate_check(aenv, dupl_hash,
+ sizeof(dupl_hash),
+ &duplicate);
+ if (ret < SIEVE_EXEC_OK) {
+ sieve_result_critical(
+ aenv, "failed to check for duplicate vacation response",
+ "failed to check for duplicate vacation response%s",
+ (ret == SIEVE_EXEC_TEMP_FAILURE ?
+ " (temporaty failure)" : ""));
+ return ret;
+ }
+ if (duplicate) {
+ sieve_result_global_log(
+ aenv,
+ "discarded duplicate vacation response to <%s>",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+ }
+
+ /* Are we trying to respond to a mailing list ? */
+ hdsp = _list_headers;
+ while (*hdsp != NULL) {
+ if ((ret = mail_get_headers(mail, *hdsp, &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `%s'", *hdsp);
+ }
+
+ if (ret > 0 && headers[0] != NULL) {
+ /* Yes, bail out */
+ sieve_result_global_log(
+ aenv, "discarding vacation response "
+ "to mailinglist recipient <%s>",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+ hdsp++;
+ }
+
+ /* Is the message that we are replying to an automatic reply ? */
+ if ((ret = mail_get_headers(mail, "auto-submitted", &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `auto-submitted'");
+ }
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if (ret > 0) {
+ hdsp = headers;
+ while (*hdsp != NULL) {
+ if (strcasecmp(*hdsp, "no") != 0) {
+ sieve_result_global_log(
+ aenv, "discarding vacation response "
+ "to auto-submitted message from <%s>",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+ hdsp++;
+ }
+ }
+
+ /* Check for the (non-standard) precedence header */
+ if ((ret = mail_get_headers(mail, "precedence", &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail, "failed to read header field `precedence'");
+ }
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if (ret > 0) {
+ hdsp = headers;
+ while (*hdsp != NULL) {
+ if (strcasecmp(*hdsp, "junk") == 0 ||
+ strcasecmp(*hdsp, "bulk") == 0 ||
+ strcasecmp(*hdsp, "list") == 0) {
+ sieve_result_global_log(
+ aenv, "discarding vacation response "
+ "to precedence=%s message from <%s>",
+ *hdsp, smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+ hdsp++;
+ }
+ }
+
+ /* Check for the (non-standard) Microsoft X-Auto-Response-Suppress header */
+ if ((ret = mail_get_headers(mail, "x-auto-response-suppress",
+ &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read header field `x-auto-response-suppress'");
+ }
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if (ret > 0) {
+ hdsp = headers;
+ while (*hdsp != NULL) {
+ const char *const *flags = t_strsplit(*hdsp, ",");
+
+ while (*flags != NULL) {
+ const char *flag = t_str_trim(*flags, " \t");
+
+ if (strcasecmp(flag, "All") == 0 ||
+ strcasecmp(flag, "OOF") == 0) {
+ sieve_result_global_log(
+ aenv, "discarding vacation response to message from <%s> "
+ "(`%s' flag found in x-auto-response-suppress header)",
+ smtp_address_encode(sender), flag);
+ return SIEVE_EXEC_OK;
+ }
+ flags++;
+ }
+ hdsp++;
+ }
+ }
+
+ /* Do not reply to system addresses */
+ if (_is_system_address(sender)) {
+ sieve_result_global_log(
+ aenv, "not sending vacation response to system address <%s>",
+ smtp_address_encode(sender));
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Fetch original recipient if necessary */
+ if (config->use_original_recipient)
+ orig_recipient = sieve_message_get_orig_recipient(aenv->msgctx);
+ /* Fetch explicitly configured user email address */
+ if (svinst->user_email != NULL)
+ user_email = svinst->user_email;
+
+ /* Is the original message directly addressed to the user or the addresses
+ * specified using the :addresses tag?
+ */
+ hdsp = _my_address_headers;
+ while (*hdsp != NULL) {
+ if ((ret = mail_get_headers(mail, *hdsp, &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail, "failed to read header field `%s'",
+ *hdsp);
+ }
+ if (ret > 0 && headers[0] != NULL) {
+ /* Final recipient directly listed in headers? */
+ if (_contains_my_address(headers, recipient)) {
+ smtp_from = recipient;
+ message_address_init_from_smtp(
+ &reply_from, NULL, recipient);
+ break;
+ }
+
+ /* Original recipient directly listed in headers? */
+ if (!smtp_address_isnull(orig_recipient) &&
+ _contains_my_address(headers, orig_recipient)) {
+ smtp_from = orig_recipient;
+ message_address_init_from_smtp(
+ &reply_from, NULL, orig_recipient);
+ break;
+ }
+
+ /* User-provided :addresses listed in headers? */
+ if (ctx->addresses != NULL) {
+ bool found = FALSE;
+ const struct smtp_address * const *my_address;
+
+ my_address = ctx->addresses;
+ while (!found && *my_address != NULL) {
+ if ((found = _contains_my_address(headers, *my_address))) {
+ /* Avoid letting user determine SMTP sender directly */
+ smtp_from = (orig_recipient == NULL ?
+ recipient : orig_recipient);
+ message_address_init_from_smtp(
+ &reply_from, NULL, *my_address);
+ }
+ my_address++;
+ }
+
+ if (found) break;
+ }
+
+ /* Explicitly-configured user email address directly listed in
+ headers? */
+ if (user_email != NULL &&
+ _contains_my_address(headers, user_email)) {
+ smtp_from = user_email;
+ message_address_init_from_smtp(
+ &reply_from, NULL, smtp_from);
+ break;
+ }
+ }
+ hdsp++;
+ }
+
+ /* My address not found in the headers; we got an implicit delivery */
+ if (*hdsp == NULL) {
+ if (config->dont_check_recipient) {
+ /* Send reply from envelope recipient address */
+ smtp_from = (orig_recipient == NULL ?
+ recipient : orig_recipient);
+ if (user_email == NULL)
+ user_email = sieve_get_user_email(svinst);
+ message_address_init_from_smtp(&reply_from,
+ NULL, user_email);
+ } else {
+ const char *orig_rcpt_str = "", *user_email_str = "";
+
+ /* Bail out */
+ if (config->use_original_recipient) {
+ orig_rcpt_str =
+ t_strdup_printf("original-recipient=<%s>, ",
+ (orig_recipient == NULL ? "UNAVAILABLE" :
+ smtp_address_encode(orig_recipient)));
+ }
+
+ if (user_email != NULL) {
+ user_email_str = t_strdup_printf(
+ "user-email=<%s>, ",
+ smtp_address_encode(user_email));
+ }
+
+ sieve_result_global_log(
+ aenv, "discarding vacation response for implicitly delivered message; "
+ "no known (envelope) recipient address found in message headers "
+ "(recipient=<%s>, %s%sand%s additional `:addresses' are specified)",
+ smtp_address_encode(recipient),
+ orig_rcpt_str, user_email_str,
+ (ctx->addresses == NULL || *ctx->addresses == NULL ?
+ " no" : ""));
+ return SIEVE_EXEC_OK;
+ }
+ }
+
+ /* Send the message */
+
+ T_BEGIN {
+ ret = act_vacation_send(
+ aenv, config, ctx, sender,
+ (config->send_from_recipient ? smtp_from : NULL),
+ &reply_from);
+ } T_END;
+
+ if (ret == SIEVE_EXEC_OK) {
+ sieve_number_t seconds;
+
+ eenv->exec_status->significant_action_executed = TRUE;
+
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv);
+
+ sieve_result_event_log(aenv, e->event(),
+ "sent vacation response to <%s>",
+ smtp_address_encode(sender));
+
+ /* Check period limits once more */
+ seconds = ctx->seconds;
+ if (seconds < config->min_period)
+ seconds = config->min_period;
+ else if (config->max_period > 0 && seconds > config->max_period)
+ seconds = config->max_period;
+
+ /* Mark as replied */
+ if (seconds > 0) {
+ sieve_action_duplicate_mark(aenv, dupl_hash,
+ sizeof(dupl_hash),
+ ioloop_time + seconds);
+ }
+ }
+
+ if (ret == SIEVE_EXEC_TEMP_FAILURE)
+ return SIEVE_EXEC_TEMP_FAILURE;
+
+ /* Ignore all other errors */
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.c b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.c
new file mode 100644
index 0000000..97be3a5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.c
@@ -0,0 +1,114 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "ext-vacation-common.h"
+
+bool ext_vacation_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_vacation_config *config;
+ sieve_number_t min_period, max_period, default_period;
+ bool use_original_recipient, dont_check_recipient, send_from_recipient,
+ to_header_ignore_envelope;
+ unsigned long long max_subject_codepoints;
+ const char *default_subject, *default_subject_template;
+
+ if ( *context != NULL ) {
+ ext_vacation_unload(ext);
+ }
+
+ if ( !sieve_setting_get_duration_value
+ (svinst, "sieve_vacation_min_period", &min_period) ) {
+ min_period = EXT_VACATION_DEFAULT_MIN_PERIOD;
+ }
+
+ if ( !sieve_setting_get_duration_value
+ (svinst, "sieve_vacation_max_period", &max_period) ) {
+ max_period = EXT_VACATION_DEFAULT_MAX_PERIOD;
+ }
+
+ if ( !sieve_setting_get_duration_value
+ (svinst, "sieve_vacation_default_period", &default_period) ) {
+ default_period = EXT_VACATION_DEFAULT_PERIOD;
+ }
+
+ if ( max_period > 0
+ && (min_period > max_period || default_period < min_period
+ || default_period > max_period) ) {
+ min_period = EXT_VACATION_DEFAULT_MIN_PERIOD;
+ max_period = EXT_VACATION_DEFAULT_MAX_PERIOD;
+ default_period = EXT_VACATION_DEFAULT_PERIOD;
+
+ e_warning(svinst->event, "vacation extension: "
+ "invalid settings: violated "
+ "sieve_vacation_min_period < "
+ "sieve_vacation_default_period < "
+ "sieve_vacation_max_period");
+ }
+
+ default_subject = sieve_setting_get(
+ svinst, "sieve_vacation_default_subject");
+ default_subject_template = sieve_setting_get(
+ svinst, "sieve_vacation_default_subject_template");
+
+ if ( !sieve_setting_get_uint_value
+ (svinst, "sieve_vacation_max_subject_codepoints", &max_subject_codepoints) ) {
+ max_subject_codepoints = EXT_VACATION_DEFAULT_MAX_SUBJECT_CODEPOINTS;
+ }
+
+ if ( !sieve_setting_get_bool_value
+ (svinst, "sieve_vacation_use_original_recipient", &use_original_recipient) ) {
+ use_original_recipient = FALSE;
+ }
+
+ if ( !sieve_setting_get_bool_value
+ (svinst, "sieve_vacation_dont_check_recipient", &dont_check_recipient) ) {
+ dont_check_recipient = FALSE;
+ }
+
+ if ( !sieve_setting_get_bool_value
+ (svinst, "sieve_vacation_send_from_recipient", &send_from_recipient) ) {
+ send_from_recipient = FALSE;
+ }
+
+ if ( !sieve_setting_get_bool_value(svinst,
+ "sieve_vacation_to_header_ignore_envelope",
+ &to_header_ignore_envelope) ) {
+ to_header_ignore_envelope = FALSE;
+ }
+
+ config = i_new(struct ext_vacation_config, 1);
+ config->min_period = min_period;
+ config->max_period = max_period;
+ config->default_period = default_period;
+ config->max_subject_codepoints = max_subject_codepoints;
+ config->default_subject = i_strdup_empty(default_subject);
+ config->default_subject_template = i_strdup_empty(default_subject_template);
+ config->use_original_recipient = use_original_recipient;
+ config->dont_check_recipient = dont_check_recipient;
+ config->send_from_recipient = send_from_recipient;
+ config->to_header_ignore_envelope = to_header_ignore_envelope;
+
+ *context = (void *) config;
+
+ return TRUE;
+}
+
+void ext_vacation_unload
+(const struct sieve_extension *ext)
+{
+ struct ext_vacation_config *config =
+ (struct ext_vacation_config *) ext->context;
+
+ i_free(config->default_subject);
+ i_free(config->default_subject_template);
+ i_free(config);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.h b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.h
new file mode 100644
index 0000000..3a38cf6
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-common.h
@@ -0,0 +1,60 @@
+#ifndef EXT_VACATION_COMMON_H
+#define EXT_VACATION_COMMON_H
+
+#include "sieve-common.h"
+
+/*
+ * Extension configuration
+ */
+
+#define EXT_VACATION_DEFAULT_PERIOD (7*24*60*60)
+#define EXT_VACATION_DEFAULT_MIN_PERIOD (24*60*60)
+#define EXT_VACATION_DEFAULT_MAX_PERIOD 0
+#define EXT_VACATION_DEFAULT_MAX_SUBJECT_CODEPOINTS 256
+
+struct ext_vacation_config {
+ unsigned int min_period;
+ unsigned int max_period;
+ unsigned int default_period;
+ unsigned long long max_subject_codepoints;
+ char *default_subject;
+ char *default_subject_template;
+ bool use_original_recipient;
+ bool dont_check_recipient;
+ bool send_from_recipient;
+ bool to_header_ignore_envelope;
+};
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def vacation_command;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def vacation_operation;
+
+/*
+ * Extensions
+ */
+
+/* Vacation */
+
+extern const struct sieve_extension_def vacation_extension;
+
+bool ext_vacation_load
+ (const struct sieve_extension *ext, void **context);
+void ext_vacation_unload
+ (const struct sieve_extension *ext);
+
+/* Vacation-seconds */
+
+extern const struct sieve_extension_def vacation_seconds_extension;
+
+bool ext_vacation_register_seconds_tag
+ (struct sieve_validator *valdtr, const struct sieve_extension *vacation_ext);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-seconds.c b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-seconds.c
new file mode 100644
index 0000000..41c0f06
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation-seconds.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vacation-seconds
+ * --------------------------
+ *
+ * Authors: Stephan Bosch <stephan@rename-it.nl>
+ * Specification: RFC 6131
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+
+#include "ext-vacation-common.h"
+
+/*
+ * Extension
+ */
+
+bool ext_vacation_seconds_load
+ (const struct sieve_extension *ext, void **context);
+static bool ext_vacation_seconds_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def vacation_seconds_extension = {
+ .name = "vacation-seconds",
+ .load = ext_vacation_seconds_load,
+ .validator_load = ext_vacation_seconds_validator_load,
+};
+
+bool ext_vacation_seconds_load
+(const struct sieve_extension *ext, void **context)
+{
+ if ( *context == NULL ) {
+ /* Make sure vacation extension is registered */
+ *context = (void *)
+ sieve_extension_require(ext->svinst, &vacation_extension, TRUE);
+ }
+
+ return TRUE;
+}
+
+static bool ext_vacation_seconds_validator_load
+(const struct sieve_extension *ext ATTR_UNUSED, struct sieve_validator *valdtr)
+{
+ const struct sieve_extension *vacation_ext;
+
+ /* Load vacation extension implicitly */
+
+ vacation_ext = sieve_validator_extension_load_implicit
+ (valdtr, vacation_extension.name);
+
+ if ( vacation_ext == NULL )
+ return FALSE;
+
+ /* Add seconds tag to vacation command */
+
+ return ext_vacation_register_seconds_tag(valdtr, vacation_ext);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation.c b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation.c
new file mode 100644
index 0000000..8d3d9a7
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vacation/ext-vacation.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vacation
+ * ------------------
+ *
+ * Authors: Stephan Bosch <stephan@rename-it.nl>
+ * Specification: RFC 5230
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-vacation-common.h"
+
+/*
+ * Extension
+ */
+
+static bool
+ext_vacation_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr);
+static bool
+ext_vacation_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+static bool
+ext_vacation_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ bool required);
+static int
+ext_vacation_interpreter_run(const struct sieve_extension *this_ext,
+ const struct sieve_runtime_env *renv,
+ void *context, bool deferred);
+
+const struct sieve_extension_def vacation_extension = {
+ .name = "vacation",
+ .load = ext_vacation_load,
+ .unload = ext_vacation_unload,
+ .validator_load = ext_vacation_validator_load,
+ .interpreter_load = ext_vacation_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATION(vacation_operation)
+};
+const struct sieve_validator_extension
+vacation_validator_extension = {
+ .ext = &vacation_extension,
+ .validate = ext_vacation_validator_validate
+};
+const struct sieve_interpreter_extension
+vacation_interpreter_extension = {
+ .ext_def = &vacation_extension,
+ .run = ext_vacation_interpreter_run
+};
+
+static bool
+ext_vacation_validator_load(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr)
+{
+ /* Register new command */
+ sieve_validator_register_command(valdtr, ext, &vacation_command);
+
+ sieve_validator_extension_register(valdtr, ext,
+ &vacation_validator_extension, NULL);
+ return TRUE;
+}
+
+static bool
+ext_vacation_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_interpreter_extension_register(
+ renv->interp, ext, &vacation_interpreter_extension, NULL);
+ return TRUE;
+}
+
+static bool
+ext_vacation_validator_validate(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr,
+ void *context ATTR_UNUSED,
+ struct sieve_ast_argument *require_arg,
+ bool required)
+{
+ if (required) {
+ enum sieve_compile_flags flags =
+ sieve_validator_compile_flags(valdtr);
+
+ if ((flags & SIEVE_COMPILE_FLAG_NO_ENVELOPE) != 0) {
+ sieve_argument_validate_error(
+ valdtr, require_arg,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static int
+ext_vacation_interpreter_run(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context ATTR_UNUSED, bool deferred)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0) {
+ if (!deferred) {
+ sieve_runtime_error(
+ renv, NULL,
+ "the %s extension cannot be used in this context "
+ "(needs access to message envelope)",
+ sieve_extension_name(ext));
+ }
+ return SIEVE_EXEC_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/Makefile.am b/pigeonhole/src/lib-sieve/plugins/variables/Makefile.am
new file mode 100644
index 0000000..354bad2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/Makefile.am
@@ -0,0 +1,41 @@
+noinst_LTLIBRARIES = libsieve_ext_variables.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-set.c
+
+tsts = \
+ tst-string.c
+
+libsieve_ext_variables_la_SOURCES = \
+ ext-variables-common.c \
+ ext-variables-name.c \
+ ext-variables-namespaces.c \
+ ext-variables-arguments.c \
+ ext-variables-operands.c \
+ ext-variables-modifiers.c \
+ ext-variables-dump.c \
+ $(cmds) \
+ $(tsts) \
+ ext-variables.c
+
+public_headers = \
+ sieve-ext-variables.h
+
+headers = \
+ ext-variables-common.h \
+ ext-variables-limits.h \
+ ext-variables-name.h \
+ ext-variables-namespaces.h \
+ ext-variables-arguments.h \
+ ext-variables-operands.h \
+ ext-variables-modifiers.h \
+ ext-variables-dump.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in b/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in
new file mode 100644
index 0000000..701d574
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in
@@ -0,0 +1,801 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/variables
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_lib_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_variables_la_LIBADD =
+am__objects_1 = cmd-set.lo
+am__objects_2 = tst-string.lo
+am_libsieve_ext_variables_la_OBJECTS = ext-variables-common.lo \
+ ext-variables-name.lo ext-variables-namespaces.lo \
+ ext-variables-arguments.lo ext-variables-operands.lo \
+ ext-variables-modifiers.lo ext-variables-dump.lo \
+ $(am__objects_1) $(am__objects_2) ext-variables.lo
+libsieve_ext_variables_la_OBJECTS = \
+ $(am_libsieve_ext_variables_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-set.Plo \
+ ./$(DEPDIR)/ext-variables-arguments.Plo \
+ ./$(DEPDIR)/ext-variables-common.Plo \
+ ./$(DEPDIR)/ext-variables-dump.Plo \
+ ./$(DEPDIR)/ext-variables-modifiers.Plo \
+ ./$(DEPDIR)/ext-variables-name.Plo \
+ ./$(DEPDIR)/ext-variables-namespaces.Plo \
+ ./$(DEPDIR)/ext-variables-operands.Plo \
+ ./$(DEPDIR)/ext-variables.Plo ./$(DEPDIR)/tst-string.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_variables_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_variables_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(noinst_HEADERS) $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_variables.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+cmds = \
+ cmd-set.c
+
+tsts = \
+ tst-string.c
+
+libsieve_ext_variables_la_SOURCES = \
+ ext-variables-common.c \
+ ext-variables-name.c \
+ ext-variables-namespaces.c \
+ ext-variables-arguments.c \
+ ext-variables-operands.c \
+ ext-variables-modifiers.c \
+ ext-variables-dump.c \
+ $(cmds) \
+ $(tsts) \
+ ext-variables.c
+
+public_headers = \
+ sieve-ext-variables.h
+
+headers = \
+ ext-variables-common.h \
+ ext-variables-limits.h \
+ ext-variables-name.h \
+ ext-variables-namespaces.h \
+ ext-variables-arguments.h \
+ ext-variables-operands.h \
+ ext-variables-modifiers.h \
+ ext-variables-dump.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(public_headers)
+noinst_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/variables/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/variables/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_variables.la: $(libsieve_ext_variables_la_OBJECTS) $(libsieve_ext_variables_la_DEPENDENCIES) $(EXTRA_libsieve_ext_variables_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_variables_la_OBJECTS) $(libsieve_ext_variables_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-set.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-arguments.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-dump.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-modifiers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-name.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-namespaces.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables-operands.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-variables.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-string.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-set.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-arguments.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-common.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-dump.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-modifiers.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-name.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-namespaces.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-operands.Plo
+ -rm -f ./$(DEPDIR)/ext-variables.Plo
+ -rm -f ./$(DEPDIR)/tst-string.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-set.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-arguments.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-common.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-dump.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-modifiers.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-name.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-namespaces.Plo
+ -rm -f ./$(DEPDIR)/ext-variables-operands.Plo
+ -rm -f ./$(DEPDIR)/ext-variables.Plo
+ -rm -f ./$(DEPDIR)/tst-string.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/cmd-set.c b/pigeonhole/src/lib-sieve/plugins/variables/cmd-set.c
new file mode 100644
index 0000000..d0fdc97
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/cmd-set.c
@@ -0,0 +1,235 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+#include "sieve-code.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * Set command
+ *
+ * Syntax:
+ * set [MODIFIER] <name: string> <value: string>
+ */
+
+static bool cmd_set_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_set_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_set_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_set = {
+ .identifier = "set",
+ .type = SCT_COMMAND,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_set_registered,
+ .validate = cmd_set_validate,
+ .generate = cmd_set_generate,
+};
+
+/*
+ * Set operation
+ */
+
+static bool cmd_set_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_set_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def cmd_set_operation = {
+ .mnemonic = "SET",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERATION_SET,
+ .dump = cmd_set_operation_dump,
+ .execute = cmd_set_operation_execute
+};
+
+/*
+ * Compiler context
+ */
+
+struct cmd_set_context {
+ ARRAY_TYPE(sieve_variables_modifier) modifiers;
+};
+
+/* Command registration */
+
+static bool cmd_set_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_variables_modifiers_link_tag(valdtr, ext, cmd_reg);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool cmd_set_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_ast_argument *arg = cmd->first_positional;
+ pool_t pool = sieve_command_pool(cmd);
+ struct cmd_set_context *sctx;
+
+ /* Create command context */
+ sctx = p_new(pool, struct cmd_set_context, 1);
+ p_array_init(&sctx->modifiers, pool, 4);
+ cmd->data = (void *) sctx;
+
+ /* Validate modifiers */
+ if ( !sieve_variables_modifiers_validate
+ (valdtr, cmd, &sctx->modifiers) )
+ return FALSE;
+
+ /* Validate name argument */
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "name", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+ if ( !sieve_variable_argument_activate
+ (this_ext, this_ext, valdtr, cmd, arg, TRUE) ) {
+ return FALSE;
+ }
+ arg = sieve_ast_argument_next(arg);
+
+ /* Validate value argument */
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+ return FALSE;
+ }
+ return sieve_validator_argument_activate
+ (valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_set_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = cmd->ext;
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ struct cmd_set_context *sctx = (struct cmd_set_context *) cmd->data;
+
+ sieve_operation_emit(sblock, this_ext, &cmd_set_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ /* Generate modifiers */
+ if ( !sieve_variables_modifiers_generate
+ (cgenv, &sctx->modifiers) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_set_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "SET");
+ sieve_code_descend(denv);
+
+ /* Print both variable name and string value */
+ if ( !sieve_opr_string_dump(denv, address, "variable") ||
+ !sieve_opr_string_dump(denv, address, "value") )
+ return FALSE;
+
+ return sieve_variables_modifiers_code_dump(denv, address);
+}
+
+/*
+ * Code execution
+ */
+
+static int cmd_set_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct sieve_variable_storage *storage;
+ ARRAY_TYPE(sieve_variables_modifier) modifiers;
+ unsigned int var_index;
+ string_t *value;
+ int ret = SIEVE_EXEC_OK;
+
+ /*
+ * Read the normal operands
+ */
+
+ if ( (ret=sieve_variable_operand_read
+ (renv, address, "variable", &storage, &var_index)) <= 0 )
+ return ret;
+
+ if ( (ret=sieve_opr_string_read(renv, address, "string", &value)) <= 0 )
+ return ret;
+
+ if ( (ret=sieve_variables_modifiers_code_read
+ (renv, this_ext, address, &modifiers)) <= 0 )
+ return ret;
+
+ /*
+ * Determine and assign the value
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "set command");
+ sieve_runtime_trace_descend(renv);
+
+ /* Apply modifiers */
+ if ( (ret=sieve_variables_modifiers_apply
+ (renv, this_ext, &modifiers, &value)) <= 0 )
+ return ret;
+
+ /* Actually assign the value if all is well */
+ i_assert ( value != NULL );
+ if ( !sieve_variable_assign(storage, var_index, value) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ /* Trace */
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ const char *var_name, *var_id;
+
+ (void)sieve_variable_get_identifier(storage, var_index, &var_name);
+ var_id = sieve_variable_get_varid(storage, var_index);
+
+ sieve_runtime_trace_here(renv, 0, "assign `%s' [%s] = \"%s\"",
+ var_name, var_id, str_c(value));
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.c
new file mode 100644
index 0000000..2ac773c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.c
@@ -0,0 +1,420 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-operands.h"
+#include "ext-variables-namespaces.h"
+#include "ext-variables-arguments.h"
+
+/*
+ * Variable argument implementation
+ */
+
+static bool arg_variable_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def variable_argument = {
+ .identifier = "@variable",
+ .generate = arg_variable_generate
+};
+
+static bool ext_variables_variable_argument_activate
+(const struct sieve_extension *var_ext,
+ const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ const char *variable)
+{
+ struct sieve_ast *ast = arg->ast;
+ struct sieve_variable *var;
+
+ var = ext_variables_validator_declare_variable(this_ext, valdtr, variable);
+
+ if ( var == NULL ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "(implicit) declaration of new variable '%s' exceeds the limit "
+ "(max variables: %u)", variable,
+ sieve_variables_get_max_scope_size(var_ext));
+ return FALSE;
+ }
+
+ arg->argument = sieve_argument_create(ast, &variable_argument, this_ext, 0);
+ arg->argument->data = (void *) var;
+ return TRUE;
+}
+
+static struct sieve_ast_argument *ext_variables_variable_argument_create
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+ struct sieve_ast_argument *parent_arg, const char *variable)
+{
+ struct sieve_ast *ast = parent_arg->ast;
+ struct sieve_ast_argument *new_arg;
+
+ new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+ new_arg->type = SAAT_STRING;
+
+ if ( !ext_variables_variable_argument_activate
+ (this_ext, this_ext, valdtr, new_arg, variable) )
+ return NULL;
+
+ return new_arg;
+}
+
+static bool arg_variable_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED)
+{
+ struct sieve_argument *argument = arg->argument;
+ struct sieve_variable *var = (struct sieve_variable *) argument->data;
+
+ sieve_variables_opr_variable_emit(cgenv->sblock, argument->ext, var);
+
+ return TRUE;
+}
+
+/*
+ * Match value argument implementation
+ */
+
+static bool arg_match_value_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED);
+
+const struct sieve_argument_def match_value_argument = {
+ .identifier = "@match_value",
+ .generate = arg_match_value_generate
+};
+
+static bool ext_variables_match_value_argument_activate
+(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ unsigned int index, bool assignment)
+{
+ struct sieve_ast *ast = arg->ast;
+
+ if ( assignment ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "cannot assign to match variable");
+ return FALSE;
+ }
+
+ if ( index > EXT_VARIABLES_MAX_MATCH_INDEX ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "match value index %u out of range (max: %u)", index,
+ EXT_VARIABLES_MAX_MATCH_INDEX);
+ return FALSE;
+ }
+
+ arg->argument = sieve_argument_create
+ (ast, &match_value_argument, this_ext, 0);
+ arg->argument->data = (void *) POINTER_CAST(index);
+ return TRUE;
+}
+
+static struct sieve_ast_argument *ext_variables_match_value_argument_create
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+ struct sieve_ast_argument *parent_arg, unsigned int index)
+{
+ struct sieve_ast *ast = parent_arg->ast;
+ struct sieve_ast_argument *new_arg;
+
+ new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+ new_arg->type = SAAT_STRING;
+
+ if ( !ext_variables_match_value_argument_activate
+ (this_ext, valdtr, new_arg, index, FALSE) ) {
+ return NULL;
+ }
+
+ return new_arg;
+}
+
+static bool arg_match_value_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED)
+{
+ struct sieve_argument *argument = arg->argument;
+ unsigned int index = POINTER_CAST_TO(argument->data, unsigned int);
+
+ sieve_variables_opr_match_value_emit(cgenv->sblock, argument->ext, index);
+
+ return TRUE;
+}
+
+/*
+ * Variable string argument implementation
+ */
+
+static bool arg_variable_string_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+const struct sieve_argument_def variable_string_argument = {
+ .identifier = "@variable-string",
+ .validate = arg_variable_string_validate,
+ .generate = sieve_arg_catenated_string_generate,
+};
+
+static bool arg_variable_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ const struct sieve_extension *this_ext = (*arg)->argument->ext;
+ enum { ST_NONE, ST_OPEN, ST_VARIABLE, ST_CLOSE } state = ST_NONE;
+ pool_t pool = sieve_ast_pool((*arg)->ast);
+ struct sieve_arg_catenated_string *catstr = NULL;
+ string_t *str = sieve_ast_argument_str(*arg);
+ const char *p, *strstart, *substart = NULL;
+ const char *strval = (const char *) str_data(str);
+ const char *strend = strval + str_len(str);
+ bool result = TRUE;
+ ARRAY_TYPE(sieve_variable_name) substitution;
+ int nelements = 0;
+
+ T_BEGIN {
+ /* Initialize substitution structure */
+ t_array_init(&substitution, 2);
+
+ p = strval;
+ strstart = p;
+ while ( result && p < strend ) {
+ switch ( state ) {
+
+ /* Nothing found yet */
+ case ST_NONE:
+ if ( *p == '$' ) {
+ substart = p;
+ state = ST_OPEN;
+ }
+ p++;
+ break;
+
+ /* Got '$' */
+ case ST_OPEN:
+ if ( *p == '{' ) {
+ state = ST_VARIABLE;
+ p++;
+ } else
+ state = ST_NONE;
+ break;
+
+ /* Got '${' */
+ case ST_VARIABLE:
+ nelements = ext_variable_name_parse(&substitution, &p, strend);
+
+ if ( nelements < 0 )
+ state = ST_NONE;
+ else
+ state = ST_CLOSE;
+
+ break;
+
+ /* Finished parsing name, expecting '}' */
+ case ST_CLOSE:
+ if ( *p == '}' ) {
+ struct sieve_ast_argument *strarg;
+
+ /* We now know that the substitution is valid */
+
+ if ( catstr == NULL ) {
+ catstr = sieve_arg_catenated_string_create(*arg);
+ }
+
+ /* Add the substring that is before the substitution to the
+ * variable-string AST.
+ *
+ * FIXME: For efficiency, if the variable is not found we should
+ * coalesce this substring with the one after the substitution.
+ */
+ if ( substart > strstart ) {
+ string_t *newstr = str_new(pool, substart - strstart);
+ str_append_data(newstr, strstart, substart - strstart);
+
+ strarg = sieve_ast_argument_string_create_raw
+ ((*arg)->ast, newstr, (*arg)->source_line);
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+
+ /* Give other substitution extensions a chance to do their work */
+ if ( !sieve_validator_argument_activate_super
+ (valdtr, cmd, strarg, FALSE) ) {
+ result = FALSE;
+ break;
+ }
+ }
+
+ /* Find the variable */
+ if ( nelements == 1 ) {
+ const struct sieve_variable_name *cur_element =
+ array_idx(&substitution, 0);
+
+ if ( cur_element->num_variable == -1 ) {
+ /* Add variable argument '${identifier}' */
+
+ strarg = ext_variables_variable_argument_create
+ (this_ext, valdtr, *arg, str_c(cur_element->identifier));
+
+ } else {
+ /* Add match value argument '${000}' */
+
+ strarg = ext_variables_match_value_argument_create
+ (this_ext, valdtr, *arg, cur_element->num_variable);
+ }
+ } else {
+ strarg = ext_variables_namespace_argument_create
+ (this_ext, valdtr, *arg, cmd, &substitution);
+ }
+
+ if ( strarg != NULL )
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+
+ strstart = p + 1;
+ substart = strstart;
+
+ p++;
+ }
+
+ /* Finished, reset for the next substitution */
+ state = ST_NONE;
+ }
+ }
+ } T_END;
+
+ /* Bail out early if substitution is invalid */
+ if ( !result ) return FALSE;
+
+ /* Check whether any substitutions were found */
+ if ( catstr == NULL ) {
+ /* No substitutions in this string, pass it on to any other substution
+ * extension.
+ */
+ return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE);
+ }
+
+ /* Add the final substring that comes after the last substitution to the
+ * variable-string AST.
+ */
+ if ( strend > strstart ) {
+ struct sieve_ast_argument *strarg;
+ string_t *newstr = str_new(pool, strend - strstart);
+ str_append_data(newstr, strstart, strend - strstart);
+
+ strarg = sieve_ast_argument_string_create_raw
+ ((*arg)->ast, newstr, (*arg)->source_line);
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+
+ /* Give other substitution extensions a chance to do their work */
+ if ( !sieve_validator_argument_activate_super
+ (valdtr, cmd, strarg, FALSE) )
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Variable argument interface
+ */
+
+static bool _sieve_variable_argument_activate
+(const struct sieve_extension *var_ext,
+ const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, bool assignment)
+{
+ bool result = FALSE;
+ string_t *variable;
+ const char *varstr, *varend;
+ ARRAY_TYPE(sieve_variable_name) vname;
+ int nelements = 0;
+
+ T_BEGIN {
+ t_array_init(&vname, 2);
+
+ variable = sieve_ast_argument_str(arg);
+ varstr = str_c(variable);
+ varend = PTR_OFFSET(varstr, str_len(variable));
+ nelements = ext_variable_name_parse(&vname, &varstr, varend);
+
+ /* Check whether name parsing succeeded */
+ if ( nelements <= 0 || varstr != varend ) {
+ /* Parse failed */
+ sieve_argument_validate_error(valdtr, arg,
+ "invalid variable name '%s'", str_sanitize(str_c(variable),80));
+ } else if ( nelements == 1 ) {
+ /* Normal (match) variable */
+
+ const struct sieve_variable_name *cur_element =
+ array_idx(&vname, 0);
+
+ if ( cur_element->num_variable < 0 ) {
+ /* Variable */
+ result = ext_variables_variable_argument_activate(var_ext,
+ this_ext, valdtr, arg, str_c(cur_element->identifier));
+
+ } else {
+ /* Match value */
+ result = ext_variables_match_value_argument_activate
+ (this_ext, valdtr, arg, cur_element->num_variable, assignment);
+ }
+
+ } else {
+ /* Namespace variable */
+ result = ext_variables_namespace_argument_activate
+ (this_ext, valdtr, arg, cmd, &vname, assignment);
+ }
+ } T_END;
+
+ return result;
+}
+
+bool sieve_variable_argument_activate
+(const struct sieve_extension *var_ext,
+ const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, bool assignment)
+{
+ if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+ /* Single string */
+ return _sieve_variable_argument_activate(var_ext,
+ this_ext, valdtr, cmd, arg, assignment);
+
+ } else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+ /* String list */
+ struct sieve_ast_argument *stritem;
+
+ i_assert ( !assignment );
+
+ stritem = sieve_ast_strlist_first(arg);
+ while ( stritem != NULL ) {
+ if ( !_sieve_variable_argument_activate(var_ext,
+ this_ext, valdtr, cmd, stritem, assignment) )
+ return FALSE;
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+
+ arg->argument = sieve_argument_create
+ (arg->ast, &string_list_argument, NULL, 0);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.h
new file mode 100644
index 0000000..87413c8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-arguments.h
@@ -0,0 +1,24 @@
+#ifndef EXT_VARIABLES_ARGUMENTS_H
+#define EXT_VARIABLES_ARGUMENTS_H
+
+#include "sieve-common.h"
+
+/*
+ * Variable argument
+ */
+
+extern const struct sieve_argument_def variable_argument;
+
+/*
+ * Match value argument
+ */
+
+extern const struct sieve_argument_def match_value_argument;
+
+/*
+ * Variable string argument
+ */
+
+extern const struct sieve_argument_def variable_string_argument;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.c
new file mode 100644
index 0000000..be9f677
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.c
@@ -0,0 +1,950 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-objects.h"
+#include "sieve-match-types.h"
+
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-modifiers.h"
+
+/*
+ * Limits
+ */
+
+unsigned int
+sieve_variables_get_max_scope_size(const struct sieve_extension *var_ext)
+{
+ const struct ext_variables_config *config =
+ ext_variables_get_config(var_ext);
+
+ return config->max_scope_size;
+}
+
+size_t
+sieve_variables_get_max_variable_size(const struct sieve_extension *var_ext)
+{
+ const struct ext_variables_config *config =
+ ext_variables_get_config(var_ext);
+
+ return config->max_variable_size;
+}
+
+/*
+ * Extension configuration
+ */
+
+bool
+ext_variables_load(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_variables_config *config;
+ unsigned long long int uint_setting;
+ size_t size_setting;
+
+ if (*context != NULL)
+ ext_variables_unload(ext);
+
+ config = i_new(struct ext_variables_config, 1);
+
+ /* Get limits */
+ config->max_scope_size = EXT_VARIABLES_DEFAULT_MAX_SCOPE_SIZE;
+ config->max_variable_size = EXT_VARIABLES_DEFAULT_MAX_VARIABLE_SIZE;
+
+ if (sieve_setting_get_uint_value(
+ svinst, "sieve_variables_max_scope_size", &uint_setting)) {
+ if (uint_setting < EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE) {
+ e_warning(svinst->event, "variables: "
+ "setting sieve_variables_max_scope_size "
+ "is lower than required by standards "
+ "(>= %llu items)",
+ (unsigned long long)EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE);
+ } else {
+ config->max_scope_size = (unsigned int)uint_setting;
+ }
+ }
+
+ if (sieve_setting_get_size_value(
+ svinst, "sieve_variables_max_variable_size", &size_setting)) {
+ if (size_setting < EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE) {
+ e_warning(svinst->event, "variables: "
+ "setting sieve_variables_max_variable_size "
+ "is lower than required by standards "
+ "(>= %zu bytes)",
+ (size_t)EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE);
+ } else {
+ config->max_variable_size = size_setting;
+ }
+ }
+
+ *context = (void *)config;
+ return TRUE;
+}
+
+void ext_variables_unload(const struct sieve_extension *ext)
+{
+ struct ext_variables_config *config =
+ (struct ext_variables_config *)ext->context;
+
+ i_free(config);
+}
+
+const struct ext_variables_config *
+ext_variables_get_config(const struct sieve_extension *var_ext)
+{
+ const struct ext_variables_config *config =
+ (const struct ext_variables_config *)var_ext->context;
+
+ i_assert(var_ext->def == &variables_extension);
+ return config;
+}
+
+/*
+ * Variable scope
+ */
+
+struct sieve_variable_scope {
+ pool_t pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+ const struct sieve_extension *var_ext;
+ const struct sieve_extension *ext;
+
+ struct sieve_variable *error_var;
+
+ HASH_TABLE(const char *, struct sieve_variable *) variables;
+ ARRAY(struct sieve_variable *) variable_index;
+};
+
+struct sieve_variable_scope_binary {
+ struct sieve_variable_scope *scope;
+
+ unsigned int size;
+ struct sieve_binary_block *sblock;
+ sieve_size_t address;
+};
+
+struct sieve_variable_scope_iter {
+ struct sieve_variable_scope *scope;
+ struct hash_iterate_context *hctx;
+};
+
+struct sieve_variable_scope *
+sieve_variable_scope_create(struct sieve_instance *svinst,
+ const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext)
+{
+ struct sieve_variable_scope *scope;
+ pool_t pool;
+
+ i_assert(var_ext->def == &variables_extension);
+
+ pool = pool_alloconly_create("sieve_variable_scope", 4096);
+ scope = p_new(pool, struct sieve_variable_scope, 1);
+ scope->pool = pool;
+ scope->refcount = 1;
+
+ scope->svinst = svinst;
+ scope->var_ext = var_ext;
+ scope->ext = ext;
+
+ hash_table_create(&scope->variables, pool, 0, strcase_hash, strcasecmp);
+ p_array_init(&scope->variable_index, pool, 128);
+
+ return scope;
+}
+
+void sieve_variable_scope_ref(struct sieve_variable_scope *scope)
+{
+ scope->refcount++;
+}
+
+void sieve_variable_scope_unref(struct sieve_variable_scope **_scope)
+{
+ struct sieve_variable_scope *scope = *_scope;
+
+ i_assert(scope->refcount > 0);
+
+ if (--scope->refcount != 0)
+ return;
+
+ hash_table_destroy(&scope->variables);
+
+ *_scope = NULL;
+ pool_unref(&scope->pool);
+}
+
+pool_t sieve_variable_scope_pool(struct sieve_variable_scope *scope)
+{
+ return scope->pool;
+}
+
+struct sieve_variable *
+sieve_variable_scope_declare(struct sieve_variable_scope *scope,
+ const char *identifier)
+{
+ unsigned int max_scope_size;
+ struct sieve_variable *var;
+
+ var = hash_table_lookup(scope->variables, identifier);
+ if (var != NULL)
+ return var;
+
+ max_scope_size = sieve_variables_get_max_scope_size(scope->var_ext);
+ if (array_count(&scope->variable_index) >= max_scope_size) {
+ if (scope->error_var == NULL) {
+ var = p_new(scope->pool, struct sieve_variable, 1);
+ var->identifier = "@ERROR@";
+ var->index = 0;
+
+ scope->error_var = var;
+ return NULL;
+ }
+
+ return scope->error_var;
+ }
+
+ var = p_new(scope->pool, struct sieve_variable, 1);
+ var->ext = scope->ext;
+ var->identifier = p_strdup(scope->pool, identifier);
+ var->index = array_count(&scope->variable_index);
+
+ hash_table_insert(scope->variables, var->identifier, var);
+ array_append(&scope->variable_index, &var, 1);
+ return var;
+}
+
+struct sieve_variable *
+sieve_variable_scope_get_variable(struct sieve_variable_scope *scope,
+ const char *identifier)
+{
+ return hash_table_lookup(scope->variables, identifier);
+}
+
+struct sieve_variable *
+sieve_variable_scope_import(struct sieve_variable_scope *scope,
+ struct sieve_variable *var)
+{
+ struct sieve_variable *old_var, *new_var;
+
+ old_var = sieve_variable_scope_get_variable(scope, var->identifier);
+ if (old_var != NULL) {
+ i_assert(memcmp(old_var, var, sizeof(*var)) == 0);
+ return old_var;
+ }
+
+ new_var = p_new(scope->pool, struct sieve_variable, 1);
+ memcpy(new_var, var, sizeof(*new_var));
+
+ hash_table_insert(scope->variables, new_var->identifier, new_var);
+
+ /* Not entered into the index because it is an external variable
+ (This can be done unlimited; only limited by the size of the external
+ scope)
+ */
+ return new_var;
+}
+
+struct sieve_variable_scope_iter *
+sieve_variable_scope_iterate_init(struct sieve_variable_scope *scope)
+{
+ struct sieve_variable_scope_iter *iter;
+
+ iter = t_new(struct sieve_variable_scope_iter, 1);
+ iter->scope = scope;
+ iter->hctx = hash_table_iterate_init(scope->variables);
+
+ return iter;
+}
+
+bool sieve_variable_scope_iterate(struct sieve_variable_scope_iter *iter,
+ struct sieve_variable **var_r)
+{
+ const char *key;
+
+ return hash_table_iterate(iter->hctx, iter->scope->variables,
+ &key, var_r);
+}
+
+void sieve_variable_scope_iterate_deinit(
+ struct sieve_variable_scope_iter **iter)
+{
+ hash_table_iterate_deinit(&(*iter)->hctx);
+ *iter = NULL;
+}
+
+unsigned int
+sieve_variable_scope_declarations(struct sieve_variable_scope *scope)
+{
+ return hash_table_count(scope->variables);
+}
+
+unsigned int sieve_variable_scope_size(struct sieve_variable_scope *scope)
+{
+ return array_count(&scope->variable_index);
+}
+
+struct sieve_variable * const *
+sieve_variable_scope_get_variables(struct sieve_variable_scope *scope,
+ unsigned int *size_r)
+{
+ return array_get(&scope->variable_index, size_r);
+}
+
+struct sieve_variable *
+sieve_variable_scope_get_indexed(struct sieve_variable_scope *scope,
+ unsigned int index)
+{
+ struct sieve_variable * const *var;
+
+ if (index >= array_count(&scope->variable_index))
+ return NULL;
+
+ var = array_idx(&scope->variable_index, index);
+ return *var;
+}
+
+/* Scope binary */
+
+struct sieve_variable_scope *
+sieve_variable_scope_binary_dump(struct sieve_instance *svinst,
+ const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ struct sieve_variable_scope *local_scope;
+ unsigned int i, scope_size;
+ sieve_size_t pc;
+ sieve_offset_t end_offset;
+
+ /* Read scope size */
+ sieve_code_mark(denv);
+ if (!sieve_binary_read_unsigned(denv->sblock, address, &scope_size))
+ return NULL;
+
+ /* Read offset */
+ pc = *address;
+ if (!sieve_binary_read_offset(denv->sblock, address, &end_offset))
+ return NULL;
+
+ /* Create scope */
+ local_scope = sieve_variable_scope_create(svinst, var_ext, ext);
+
+ /* Read and dump scope itself */
+
+ sieve_code_dumpf(denv, "VARIABLES SCOPE [%u] (end: %08x)",
+ scope_size, (unsigned int)(pc + end_offset));
+
+ for (i = 0; i < scope_size; i++) {
+ string_t *identifier;
+
+ sieve_code_mark(denv);
+ if (!sieve_binary_read_string(denv->sblock, address,
+ &identifier))
+ return NULL;
+
+ sieve_code_dumpf(denv, "%3d: '%s'", i, str_c(identifier));
+
+ (void)sieve_variable_scope_declare(local_scope,
+ str_c(identifier));
+ }
+
+ return local_scope;
+}
+
+struct sieve_variable_scope_binary *
+sieve_variable_scope_binary_create(struct sieve_variable_scope *scope)
+{
+ struct sieve_variable_scope_binary *scpbin;
+
+ scpbin = p_new(scope->pool, struct sieve_variable_scope_binary, 1);
+ scpbin->scope = scope;
+
+ return scpbin;
+}
+
+void sieve_variable_scope_binary_ref(struct sieve_variable_scope_binary *scpbin)
+{
+ sieve_variable_scope_ref(scpbin->scope);
+}
+
+void sieve_variable_scope_binary_unref(
+ struct sieve_variable_scope_binary **scpbin)
+{
+ sieve_variable_scope_unref(&(*scpbin)->scope);
+ *scpbin = NULL;
+}
+
+struct sieve_variable_scope_binary *
+sieve_variable_scope_binary_read(struct sieve_instance *svinst,
+ const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *address)
+{
+ struct sieve_variable_scope *scope;
+ struct sieve_variable_scope_binary *scpbin;
+ unsigned int scope_size, max_scope_size;
+ const char *ext_name = (ext == NULL ? "variables" :
+ sieve_extension_name(ext));
+ sieve_size_t pc;
+ sieve_offset_t end_offset;
+
+ /* Read scope size */
+ if (!sieve_binary_read_unsigned(sblock, address, &scope_size)) {
+ e_error(svinst->event, "%s: "
+ "variable scope: failed to read size", ext_name);
+ return NULL;
+ }
+
+ /* Check size limit */
+ max_scope_size = sieve_variables_get_max_scope_size(var_ext);
+ if (scope_size > max_scope_size) {
+ e_error(svinst->event, "%s: "
+ "variable scope: size exceeds the limit (%u > %u)",
+ ext_name, scope_size, max_scope_size);
+ return NULL;
+ }
+
+ /* Read offset */
+ pc = *address;
+ if (!sieve_binary_read_offset(sblock, address, &end_offset)) {
+ e_error(svinst->event, "%s: "
+ "variable scope: failed to read end offset", ext_name);
+ return NULL;
+ }
+
+ /* Create scope */
+ scope = sieve_variable_scope_create(svinst, var_ext, ext);
+
+ scpbin = sieve_variable_scope_binary_create(scope);
+ scpbin->size = scope_size;
+ scpbin->sblock = sblock;
+ scpbin->address = *address;
+
+ *address = pc + end_offset;
+
+ return scpbin;
+}
+
+struct sieve_variable_scope *
+sieve_variable_scope_binary_get(struct sieve_variable_scope_binary *scpbin)
+{
+ const struct sieve_extension *ext = scpbin->scope->ext;
+ struct sieve_instance *svinst = scpbin->scope->svinst;
+ const char *ext_name = (ext == NULL ? "variables" :
+ sieve_extension_name(ext));
+ unsigned int i;
+
+ if (scpbin->sblock != NULL) {
+ sieve_size_t *address = &scpbin->address;
+
+ /* Read scope itself */
+ for (i = 0; i < scpbin->size; i++) {
+ struct sieve_variable *var;
+ string_t *identifier;
+
+ if (!sieve_binary_read_string(scpbin->sblock, address,
+ &identifier)) {
+ e_error(svinst->event, "%s: variable scope: "
+ "failed to read variable name",
+ ext_name);
+ return NULL;
+ }
+
+ var = sieve_variable_scope_declare(scpbin->scope,
+ str_c(identifier));
+
+ i_assert(var != NULL);
+ i_assert(var->index == i);
+ }
+
+ scpbin->sblock = NULL;
+ }
+
+ return scpbin->scope;
+}
+
+unsigned int
+sieve_variable_scope_binary_get_size(
+ struct sieve_variable_scope_binary *scpbin)
+{
+ if (scpbin->sblock != NULL)
+ return scpbin->size;
+
+ return array_count(&scpbin->scope->variable_index);
+}
+
+/*
+ * Variable storage
+ */
+
+struct sieve_variable_storage {
+ pool_t pool;
+ const struct sieve_extension *var_ext;
+ struct sieve_variable_scope *scope;
+ struct sieve_variable_scope_binary *scope_bin;
+ unsigned int max_size;
+ ARRAY(string_t *) var_values;
+};
+
+struct sieve_variable_storage *
+sieve_variable_storage_create(const struct sieve_extension *var_ext,
+ pool_t pool,
+ struct sieve_variable_scope_binary *scpbin)
+{
+ struct sieve_variable_storage *storage;
+
+ storage = p_new(pool, struct sieve_variable_storage, 1);
+ storage->pool = pool;
+ storage->var_ext = var_ext;
+ storage->scope_bin = scpbin;
+ storage->scope = NULL;
+
+ storage->max_size = sieve_variable_scope_binary_get_size(scpbin);
+
+ p_array_init(&storage->var_values, pool, 4);
+
+ return storage;
+}
+
+static inline bool
+sieve_variable_valid(struct sieve_variable_storage *storage,
+ unsigned int index)
+{
+ if (storage->scope_bin == NULL)
+ return TRUE;
+
+ return (index < storage->max_size);
+}
+
+bool sieve_variable_get_identifier(struct sieve_variable_storage *storage,
+ unsigned int index, const char **identifier)
+{
+ struct sieve_variable * const *var;
+
+ *identifier = NULL;
+
+ if (storage->scope_bin == NULL)
+ return TRUE;
+
+ if (storage->scope == NULL) {
+ storage->scope =
+ sieve_variable_scope_binary_get(storage->scope_bin);
+ if (storage->scope == NULL)
+ return FALSE;
+ }
+
+ /* FIXME: direct invasion of the scope object is a bit ugly */
+ if (index >= array_count(&storage->scope->variable_index))
+ return FALSE;
+
+ var = array_idx(&storage->scope->variable_index, index);
+ if (*var != NULL)
+ *identifier = (*var)->identifier;
+ return TRUE;
+}
+
+const char *
+sieve_variable_get_varid(struct sieve_variable_storage *storage,
+ unsigned int index)
+{
+ if (storage->scope_bin == NULL)
+ return t_strdup_printf("%ld", (long)index);
+
+ if (storage->scope == NULL) {
+ storage->scope =
+ sieve_variable_scope_binary_get(storage->scope_bin);
+ if (storage->scope == NULL)
+ return NULL;
+ }
+
+ return sieve_ext_variables_get_varid(storage->scope->ext, index);
+}
+
+bool sieve_variable_get(struct sieve_variable_storage *storage,
+ unsigned int index, string_t **value)
+{
+ *value = NULL;
+
+ if (index < array_count(&storage->var_values)) {
+ string_t * const *varent;
+
+ varent = array_idx(&storage->var_values, index);
+
+ *value = *varent;
+ } else if (!sieve_variable_valid(storage, index)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool sieve_variable_get_modifiable(struct sieve_variable_storage *storage,
+ unsigned int index, string_t **value)
+{
+ string_t *dummy;
+
+ if (value == NULL)
+ value = &dummy;
+
+ if (!sieve_variable_get(storage, index, value))
+ return FALSE;
+
+ if (*value == NULL) {
+ *value = str_new(storage->pool, 256);
+ array_idx_set(&storage->var_values, index, value);
+ }
+ return TRUE;
+}
+
+bool sieve_variable_assign(struct sieve_variable_storage *storage,
+ unsigned int index, const string_t *value)
+{
+ const struct ext_variables_config *config =
+ ext_variables_get_config(storage->var_ext);
+ string_t *varval;
+
+ if (!sieve_variable_get_modifiable(storage, index, &varval))
+ return FALSE;
+
+ str_truncate(varval, 0);
+ str_append_str(varval, value);
+
+ /* Just a precaution, caller should prevent this in the first place */
+ if (str_len(varval) > config->max_variable_size)
+ str_truncate_utf8(varval, config->max_variable_size);
+
+ return TRUE;
+}
+
+bool sieve_variable_assign_cstr(struct sieve_variable_storage *storage,
+ unsigned int index, const char *value)
+{
+ const struct ext_variables_config *config =
+ ext_variables_get_config(storage->var_ext);
+ string_t *varval;
+
+ if (!sieve_variable_get_modifiable(storage, index, &varval))
+ return FALSE;
+
+ str_truncate(varval, 0);
+ str_append(varval, value);
+
+ /* Just a precaution, caller should prevent this in the first place */
+ if (str_len(varval) > config->max_variable_size)
+ str_truncate_utf8(varval, config->max_variable_size);
+
+ return TRUE;
+}
+
+/*
+ * AST Context
+ */
+
+static void
+ext_variables_ast_free(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_ast *ast ATTR_UNUSED, void *context)
+{
+ struct sieve_variable_scope *local_scope =
+ (struct sieve_variable_scope *)context;
+
+ /* Unreference main variable scope */
+ sieve_variable_scope_unref(&local_scope);
+}
+
+static const struct sieve_ast_extension variables_ast_extension = {
+ &variables_extension,
+ ext_variables_ast_free
+};
+
+static struct sieve_variable_scope *
+ext_variables_create_local_scope(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast)
+{
+ struct sieve_variable_scope *scope;
+
+ scope = sieve_variable_scope_create(this_ext->svinst, this_ext, NULL);
+
+ sieve_ast_extension_register(ast, this_ext, &variables_ast_extension,
+ (void *)scope);
+ return scope;
+}
+
+static struct sieve_variable_scope *
+ext_variables_ast_get_local_scope(const struct sieve_extension *this_ext,
+ struct sieve_ast *ast)
+{
+ struct sieve_variable_scope *local_scope =
+ (struct sieve_variable_scope *)
+ sieve_ast_extension_get_context(ast, this_ext);
+
+ return local_scope;
+}
+
+/*
+ * Validator context
+ */
+
+static struct ext_variables_validator_context *
+ext_variables_validator_context_create(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr)
+{
+ pool_t pool = sieve_validator_pool(valdtr);
+ struct ext_variables_validator_context *ctx;
+ struct sieve_ast *ast = sieve_validator_ast(valdtr);
+
+ ctx = p_new(pool, struct ext_variables_validator_context, 1);
+ ctx->modifiers = sieve_validator_object_registry_create(valdtr);
+ ctx->namespaces = sieve_validator_object_registry_create(valdtr);
+ ctx->local_scope = ext_variables_create_local_scope(this_ext, ast);
+
+ sieve_validator_extension_set_context(valdtr, this_ext, (void *)ctx);
+ return ctx;
+}
+
+struct ext_variables_validator_context *
+ext_variables_validator_context_get(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr)
+{
+ struct ext_variables_validator_context *ctx;
+
+ i_assert(sieve_extension_is(this_ext, variables_extension));
+ ctx = (struct ext_variables_validator_context *)
+ sieve_validator_extension_get_context(valdtr, this_ext);
+
+ if (ctx == NULL)
+ ctx = ext_variables_validator_context_create(this_ext, valdtr);
+ return ctx;
+}
+
+void ext_variables_validator_initialize(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr)
+{
+ struct ext_variables_validator_context *ctx;
+
+ /* Create our context */
+ ctx = ext_variables_validator_context_get(this_ext, valdtr);
+
+ ext_variables_register_core_modifiers(this_ext, ctx);
+
+ ctx->active = TRUE;
+}
+
+struct sieve_variable *ext_variables_validator_get_variable(
+ const struct sieve_extension *this_ext,
+ struct sieve_validator *validator, const char *variable)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(this_ext, validator);
+
+ return sieve_variable_scope_get_variable(ctx->local_scope, variable);
+}
+
+struct sieve_variable *
+ext_variables_validator_declare_variable(const struct sieve_extension *this_ext,
+ struct sieve_validator *validator,
+ const char *variable)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(this_ext, validator);
+
+ return sieve_variable_scope_declare(ctx->local_scope, variable);
+}
+
+struct sieve_variable_scope *
+sieve_ext_variables_get_local_scope(const struct sieve_extension *var_ext,
+ struct sieve_validator *validator)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, validator);
+
+ return ctx->local_scope;
+}
+
+bool sieve_ext_variables_is_active(const struct sieve_extension *var_ext,
+ struct sieve_validator *valdtr)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+
+ return (ctx != NULL && ctx->active);
+}
+
+/*
+ * Code generation
+ */
+
+bool ext_variables_generator_load(const struct sieve_extension *ext,
+ const struct sieve_codegen_env *cgenv)
+{
+ struct sieve_variable_scope *local_scope =
+ ext_variables_ast_get_local_scope(ext, cgenv->ast);
+ unsigned int count = sieve_variable_scope_size(local_scope);
+ sieve_size_t jump;
+
+ sieve_binary_emit_unsigned(cgenv->sblock, count);
+
+ jump = sieve_binary_emit_offset(cgenv->sblock, 0);
+
+ if (count > 0) {
+ unsigned int size, i;
+ struct sieve_variable *const *vars =
+ sieve_variable_scope_get_variables(local_scope, &size);
+
+ for (i = 0; i < size; i++) {
+ sieve_binary_emit_cstring(cgenv->sblock,
+ vars[i]->identifier);
+ }
+ }
+
+ sieve_binary_resolve_offset(cgenv->sblock, jump);
+ return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+struct ext_variables_interpreter_context {
+ pool_t pool;
+
+ struct sieve_variable_scope *local_scope;
+ struct sieve_variable_scope_binary *local_scope_bin;
+
+ struct sieve_variable_storage *local_storage;
+ ARRAY(struct sieve_variable_storage *) ext_storages;
+};
+
+static void
+ext_variables_interpreter_free(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_interpreter *interp ATTR_UNUSED,
+ void *context)
+{
+ struct ext_variables_interpreter_context *ctx =
+ (struct ext_variables_interpreter_context *)context;
+
+ sieve_variable_scope_binary_unref(&ctx->local_scope_bin);
+}
+
+static struct sieve_interpreter_extension
+variables_interpreter_extension = {
+ .ext_def = &variables_extension,
+ .free = ext_variables_interpreter_free
+};
+
+static struct ext_variables_interpreter_context *
+ext_variables_interpreter_context_create(
+ const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp,
+ struct sieve_variable_scope_binary *scpbin)
+{
+ pool_t pool = sieve_interpreter_pool(interp);
+ struct ext_variables_interpreter_context *ctx;
+
+ ctx = p_new(pool, struct ext_variables_interpreter_context, 1);
+ ctx->pool = pool;
+ ctx->local_scope = NULL;
+ ctx->local_scope_bin = scpbin;
+ ctx->local_storage =
+ sieve_variable_storage_create(this_ext, pool, scpbin);
+ p_array_init(&ctx->ext_storages, pool,
+ sieve_extensions_get_count(this_ext->svinst));
+
+ sieve_interpreter_extension_register(interp, this_ext,
+ &variables_interpreter_extension,
+ (void *)ctx);
+ return ctx;
+}
+
+bool ext_variables_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_variable_scope_binary *scpbin;
+
+ scpbin = sieve_variable_scope_binary_read(eenv->svinst, ext, NULL,
+ renv->sblock, address);
+ if (scpbin == NULL)
+ return FALSE;
+
+ /* Create our context */
+ (void)ext_variables_interpreter_context_create(ext, renv->interp,
+ scpbin);
+
+ /* Enable support for match values */
+ (void)sieve_match_values_set_enabled(renv, TRUE);
+
+ return TRUE;
+}
+
+static inline struct ext_variables_interpreter_context *
+ext_variables_interpreter_context_get(const struct sieve_extension *this_ext,
+ struct sieve_interpreter *interp)
+{
+ struct ext_variables_interpreter_context *ctx;
+
+ i_assert(sieve_extension_is(this_ext, variables_extension));
+ ctx = (struct ext_variables_interpreter_context *)
+ sieve_interpreter_extension_get_context(interp, this_ext);
+ return ctx;
+}
+
+struct sieve_variable_storage *
+sieve_ext_variables_runtime_get_storage(const struct sieve_extension *var_ext,
+ const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext)
+{
+ struct ext_variables_interpreter_context *ctx =
+ ext_variables_interpreter_context_get(var_ext, renv->interp);
+ struct sieve_variable_storage * const *storage;
+
+ if (ext == NULL)
+ return ctx->local_storage;
+
+ if (ext->id >= (int)array_count(&ctx->ext_storages))
+ storage = NULL;
+ else
+ storage = array_idx(&ctx->ext_storages, ext->id);
+
+ if (storage == NULL)
+ return NULL;
+ return *storage;
+}
+
+void sieve_ext_variables_runtime_set_storage(
+ const struct sieve_extension *var_ext,
+ const struct sieve_runtime_env *renv, const struct sieve_extension *ext,
+ struct sieve_variable_storage *storage)
+{
+ struct ext_variables_interpreter_context *ctx =
+ ext_variables_interpreter_context_get(var_ext, renv->interp);
+
+ if (ctx == NULL || ext == NULL || storage == NULL)
+ return;
+ if (ext->id < 0)
+ return;
+
+ array_idx_set(&ctx->ext_storages, (unsigned int) ext->id, &storage);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.h
new file mode 100644
index 0000000..401d943
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-common.h
@@ -0,0 +1,102 @@
+#ifndef EXT_VARIABLES_COMMON_H
+#define EXT_VARIABLES_COMMON_H
+
+#include "sieve-common.h"
+#include "sieve-validator.h"
+
+#include "sieve-ext-variables.h"
+
+/*
+ * Extension
+ */
+
+struct ext_variables_config {
+ /* Maximum number of variables (in a scope) */
+ unsigned int max_scope_size;
+ /* Maximum size of variable value */
+ size_t max_variable_size;
+};
+
+extern const struct sieve_extension_def variables_extension;
+
+bool ext_variables_load(const struct sieve_extension *ext, void **context);
+void ext_variables_unload(const struct sieve_extension *ext);
+
+const struct ext_variables_config *
+ext_variables_get_config(const struct sieve_extension *var_ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_set;
+extern const struct sieve_command_def tst_string;
+
+/*
+ * Operands
+ */
+
+enum ext_variables_operand {
+ EXT_VARIABLES_OPERAND_VARIABLE,
+ EXT_VARIABLES_OPERAND_MATCH_VALUE,
+ EXT_VARIABLES_OPERAND_NAMESPACE_VARIABLE,
+ EXT_VARIABLES_OPERAND_MODIFIER
+};
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def cmd_set_operation;
+extern const struct sieve_operation_def tst_string_operation;
+
+enum ext_variables_opcode {
+ EXT_VARIABLES_OPERATION_SET,
+ EXT_VARIABLES_OPERATION_STRING
+};
+
+/*
+ * Validator context
+ */
+
+struct ext_variables_validator_context {
+ bool active;
+
+ struct sieve_validator_object_registry *modifiers;
+ struct sieve_validator_object_registry *namespaces;
+
+ struct sieve_variable_scope *local_scope;
+};
+
+void ext_variables_validator_initialize(const struct sieve_extension *this_ext,
+ struct sieve_validator *validator);
+
+struct ext_variables_validator_context *
+ext_variables_validator_context_get(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr);
+
+struct sieve_variable *
+ext_variables_validator_get_variable(const struct sieve_extension *this_ext,
+ struct sieve_validator *validator,
+ const char *variable);
+struct sieve_variable *
+ext_variables_validator_declare_variable(const struct sieve_extension *this_ext,
+ struct sieve_validator *validator,
+ const char *variable);
+
+/*
+ * Code generation
+ */
+
+bool ext_variables_generator_load(const struct sieve_extension *ext,
+ const struct sieve_codegen_env *cgenv);
+
+/*
+ * Interpreter context
+ */
+
+bool ext_variables_interpreter_load(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.c
new file mode 100644
index 0000000..26bd015
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.c
@@ -0,0 +1,137 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-dump.h"
+
+/*
+ * Code dumper extension
+ */
+
+static void ext_variables_code_dumper_free
+ (struct sieve_code_dumper *dumper, void *context);
+
+static const struct sieve_code_dumper_extension
+variables_dump_extension = {
+ &variables_extension,
+ ext_variables_code_dumper_free
+};
+
+/*
+ * Code dump context
+ */
+
+struct ext_variables_dump_context {
+ struct sieve_variable_scope *local_scope;
+ ARRAY(struct sieve_variable_scope *) ext_scopes;
+};
+
+static void ext_variables_code_dumper_free
+(struct sieve_code_dumper *dumper ATTR_UNUSED, void *context)
+{
+ struct ext_variables_dump_context *dctx =
+ (struct ext_variables_dump_context *) context;
+
+ if ( dctx == NULL || dctx->local_scope == NULL )
+ return;
+
+ sieve_variable_scope_unref(&dctx->local_scope);
+}
+
+static struct ext_variables_dump_context *ext_variables_dump_get_context
+(const struct sieve_extension *this_ext, const struct sieve_dumptime_env *denv)
+{
+ struct sieve_code_dumper *dumper = denv->cdumper;
+ struct ext_variables_dump_context *dctx;
+ pool_t pool;
+
+ i_assert( sieve_extension_is(this_ext, variables_extension) );
+ dctx = sieve_dump_extension_get_context(dumper, this_ext);
+
+ if ( dctx == NULL ) {
+ /* Create dumper context */
+ pool = sieve_code_dumper_pool(dumper);
+ dctx = p_new(pool, struct ext_variables_dump_context, 1);
+ p_array_init(&dctx->ext_scopes, pool,
+ sieve_extensions_get_count(this_ext->svinst));
+
+ sieve_dump_extension_register
+ (dumper, this_ext, &variables_dump_extension, dctx);
+ }
+
+ return dctx;
+}
+
+bool ext_variables_code_dump
+(const struct sieve_extension *ext,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ struct ext_variables_dump_context *dctx;
+ struct sieve_variable_scope *local_scope;
+
+ local_scope = sieve_variable_scope_binary_dump
+ (ext->svinst, ext, NULL, denv, address);
+
+ dctx = ext_variables_dump_get_context(ext, denv);
+ dctx->local_scope = local_scope;
+
+ return TRUE;
+}
+
+/*
+ * Scope registry
+ */
+
+void sieve_ext_variables_dump_set_scope
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+ const struct sieve_extension *ext, struct sieve_variable_scope *scope)
+{
+ struct ext_variables_dump_context *dctx =
+ ext_variables_dump_get_context(var_ext, denv);
+
+ if ( ext->id < 0 ) return;
+
+ array_idx_set(&dctx->ext_scopes, (unsigned int) ext->id, &scope);
+}
+
+/*
+ * Variable identifier dump
+ */
+
+const char *ext_variables_dump_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+ const struct sieve_extension *ext, unsigned int index)
+{
+ struct ext_variables_dump_context *dctx =
+ ext_variables_dump_get_context(var_ext, denv);
+ struct sieve_variable_scope *scope;
+ struct sieve_variable *var;
+
+ if ( ext == NULL )
+ scope = dctx->local_scope;
+ else {
+ struct sieve_variable_scope *const *ext_scope;
+
+ if ( ext->id < 0 || ext->id >= (int) array_count(&dctx->ext_scopes) )
+ return NULL;
+
+ ext_scope = array_idx(&dctx->ext_scopes, (unsigned int) ext->id);
+ scope = *ext_scope;
+ }
+
+ if ( scope == NULL )
+ return NULL;
+
+ var = sieve_variable_scope_get_indexed(scope, index);
+
+ return var->identifier;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.h
new file mode 100644
index 0000000..da72a5d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-dump.h
@@ -0,0 +1,22 @@
+#ifndef EXT_VARIABLES_DUMP_H
+#define EXT_VARIABLES_DUMP_H
+
+#include "sieve-common.h"
+
+/*
+ * Code dump context
+ */
+
+bool ext_variables_code_dump
+ (const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+
+/*
+ * Variable identifier dump
+ */
+
+const char *ext_variables_dump_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+ const struct sieve_extension *ext, unsigned int index);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-limits.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-limits.h
new file mode 100644
index 0000000..61260c8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-limits.h
@@ -0,0 +1,35 @@
+#ifndef EXT_VARIABLES_LIMITS_H
+#define EXT_VARIABLES_LIMITS_H
+
+#include "sieve-limits.h"
+
+/* From RFC 5229:
+ *
+ * 6. Implementation Limits
+ *
+ * An implementation of this document MUST support at least 128 distinct
+ * variables. The supported length of variable names MUST be at least
+ * 32 characters. Each variable MUST be able to hold at least 4000
+ * characters. Attempts to set the variable to a value larger than what
+ * the implementation supports SHOULD be reported as an error at
+ * compile-time if possible. If the attempt is discovered during run-
+ * time, the value SHOULD be truncated, and it MUST NOT be treated as an
+ * error.
+
+ * Match variables ${1} through ${9} MUST be supported. References to
+ * higher indices than those the implementation supports MUST be treated
+ * as a syntax error, which SHOULD be discovered at compile-time.
+ */
+
+#define EXT_VARIABLES_DEFAULT_MAX_SCOPE_SIZE 255
+#define EXT_VARIABLES_DEFAULT_MAX_VARIABLE_SIZE (4 * 1024)
+
+#define EXT_VARIABLES_REQUIRED_MAX_SCOPE_SIZE 128
+#define EXT_VARIABLES_REQUIRED_MAX_VARIABLE_SIZE 4000
+
+#define EXT_VARIABLES_MAX_VARIABLE_NAME_LEN 64
+#define EXT_VARIABLES_MAX_NAMESPACE_ELEMENTS 10
+
+#define EXT_VARIABLES_MAX_MATCH_INDEX SIEVE_MAX_MATCH_VALUES
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
new file mode 100644
index 0000000..dd21c88
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.c
@@ -0,0 +1,578 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "unichar.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-runtime.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-modifiers.h"
+
+#include <ctype.h>
+
+/*
+ * Core modifiers
+ */
+
+extern const struct sieve_variables_modifier_def lower_modifier;
+extern const struct sieve_variables_modifier_def upper_modifier;
+extern const struct sieve_variables_modifier_def lowerfirst_modifier;
+extern const struct sieve_variables_modifier_def upperfirst_modifier;
+extern const struct sieve_variables_modifier_def quotewildcard_modifier;
+extern const struct sieve_variables_modifier_def length_modifier;
+
+enum ext_variables_modifier_code {
+ EXT_VARIABLES_MODIFIER_LOWER,
+ EXT_VARIABLES_MODIFIER_UPPER,
+ EXT_VARIABLES_MODIFIER_LOWERFIRST,
+ EXT_VARIABLES_MODIFIER_UPPERFIRST,
+ EXT_VARIABLES_MODIFIER_QUOTEWILDCARD,
+ EXT_VARIABLES_MODIFIER_LENGTH
+};
+
+const struct sieve_variables_modifier_def *ext_variables_core_modifiers[] = {
+ &lower_modifier,
+ &upper_modifier,
+ &lowerfirst_modifier,
+ &upperfirst_modifier,
+ &quotewildcard_modifier,
+ &length_modifier
+};
+
+const unsigned int ext_variables_core_modifiers_count =
+ N_ELEMENTS(ext_variables_core_modifiers);
+
+#define ext_variables_modifier_name(modf) \
+ (modf)->object->def->name
+#define ext_variables_modifiers_equal(modf1, modf2) \
+ ( (modf1)->def == (modf2)->def )
+#define ext_variables_modifiers_equal_precedence(modf1, modf2) \
+ ( (modf1)->def->precedence == (modf2)->def->precendence )
+
+/*
+ * Modifier registry
+ */
+
+void sieve_variables_modifier_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_modifier_def *smodf_def)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+
+ sieve_validator_object_registry_add(ctx->modifiers, ext, &smodf_def->obj_def);
+}
+
+bool ext_variables_modifier_exists
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const char *identifier)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+
+ return sieve_validator_object_registry_find(ctx->modifiers, identifier, NULL);
+}
+
+const struct sieve_variables_modifier *ext_variables_modifier_create_instance
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *identifier)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+ struct sieve_object object;
+ struct sieve_variables_modifier *modf;
+ pool_t pool;
+
+ if ( !sieve_validator_object_registry_find
+ (ctx->modifiers, identifier, &object) )
+ return NULL;
+
+ pool = sieve_command_pool(cmd);
+ modf = p_new(pool, struct sieve_variables_modifier, 1);
+ modf->object = object;
+ modf->var_ext = var_ext;
+ modf->def = (const struct sieve_variables_modifier_def *) object.def;
+
+ return modf;
+}
+
+void ext_variables_register_core_modifiers
+(const struct sieve_extension *ext, struct ext_variables_validator_context *ctx)
+{
+ unsigned int i;
+
+ /* Register core modifiers*/
+ for ( i = 0; i < ext_variables_core_modifiers_count; i++ ) {
+ sieve_validator_object_registry_add
+ (ctx->modifiers, ext, &(ext_variables_core_modifiers[i]->obj_def));
+ }
+}
+
+/*
+ * Core modifiers
+ */
+
+/* Forward declarations */
+
+static bool
+mod_lower_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+static bool
+mod_upper_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+static bool
+mod_lowerfirst_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+static bool
+mod_upperfirst_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+static bool
+mod_length_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+static bool
+mod_quotewildcard_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+
+/* Modifier objects */
+
+const struct sieve_variables_modifier_def lower_modifier = {
+ SIEVE_OBJECT("lower", &modifier_operand, EXT_VARIABLES_MODIFIER_LOWER),
+ 40,
+ mod_lower_modify
+};
+
+const struct sieve_variables_modifier_def upper_modifier = {
+ SIEVE_OBJECT("upper", &modifier_operand, EXT_VARIABLES_MODIFIER_UPPER),
+ 40,
+ mod_upper_modify
+};
+
+const struct sieve_variables_modifier_def lowerfirst_modifier = {
+ SIEVE_OBJECT
+ ("lowerfirst", &modifier_operand, EXT_VARIABLES_MODIFIER_LOWERFIRST),
+ 30,
+ mod_lowerfirst_modify
+};
+
+const struct sieve_variables_modifier_def upperfirst_modifier = {
+ SIEVE_OBJECT
+ ("upperfirst", &modifier_operand, EXT_VARIABLES_MODIFIER_UPPERFIRST),
+ 30,
+ mod_upperfirst_modify
+};
+
+const struct sieve_variables_modifier_def quotewildcard_modifier = {
+ SIEVE_OBJECT
+ ("quotewildcard", &modifier_operand, EXT_VARIABLES_MODIFIER_QUOTEWILDCARD),
+ 20,
+ mod_quotewildcard_modify
+};
+
+const struct sieve_variables_modifier_def length_modifier = {
+ SIEVE_OBJECT("length", &modifier_operand, EXT_VARIABLES_MODIFIER_LENGTH),
+ 10,
+ mod_length_modify
+};
+
+/* Modifier implementations */
+
+static bool
+mod_upperfirst_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
+ string_t *in, string_t **result)
+{
+ char *content;
+
+ if ( str_len(in) == 0 ) {
+ *result = in;
+ return TRUE;
+ }
+
+ *result = t_str_new(str_len(in));
+ str_append_str(*result, in);
+
+ content = str_c_modifiable(*result);
+ content[0] = i_toupper(content[0]);
+
+ return TRUE;
+}
+
+static bool
+mod_lowerfirst_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
+ string_t *in, string_t **result)
+{
+ char *content;
+
+ if ( str_len(in) == 0 ) {
+ *result = in;
+ return TRUE;
+ }
+
+ *result = t_str_new(str_len(in));
+ str_append_str(*result, in);
+
+ content = str_c_modifiable(*result);
+ content[0] = i_tolower(content[0]);
+
+ return TRUE;
+}
+
+static bool
+mod_upper_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
+ string_t *in, string_t **result)
+{
+ char *content;
+
+ if ( str_len(in) == 0 ) {
+ *result = in;
+ return TRUE;
+ }
+
+ *result = t_str_new(str_len(in));
+ str_append_str(*result, in);
+
+ content = str_c_modifiable(*result);
+ (void)str_ucase(content);
+
+ return TRUE;
+}
+
+static bool
+mod_lower_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
+ string_t *in, string_t **result)
+{
+ char *content;
+
+ if ( str_len(in) == 0 ) {
+ *result = in;
+ return TRUE;
+ }
+
+ *result = t_str_new(str_len(in));
+ str_append_str(*result, in);
+
+ content = str_c_modifiable(*result);
+ (void)str_lcase(content);
+
+ return TRUE;
+}
+
+static bool
+mod_length_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
+ string_t *in, string_t **result)
+{
+ *result = t_str_new(64);
+ str_printfa(*result, "%llu", (unsigned long long)
+ uni_utf8_strlen_n(str_data(in), str_len(in)));
+ return TRUE;
+}
+
+static bool
+mod_quotewildcard_modify(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result)
+{
+ size_t max_var_size =
+ sieve_variables_get_max_variable_size(modf->var_ext);
+ const unsigned char *p, *poff, *pend;
+ size_t new_size;
+
+ if ( str_len(in) == 0 ) {
+ /* empty string */
+ *result = in;
+ return TRUE;
+ }
+
+ /* allocate new string */
+ new_size = str_len(in) + 16;
+ if (new_size > max_var_size)
+ new_size = max_var_size;
+ *result = t_str_new(new_size + 1);
+
+ /* escape string */
+ p = str_data(in);
+ pend = p + str_len(in);
+ poff = p;
+ while (p < pend) {
+ unsigned int n = uni_utf8_char_bytes((char)*p);
+
+ if (n == 1 && (*p == '*' || *p == '?' || *p == '\\')) {
+ str_append_data(*result, poff, p - poff);
+ poff = p;
+
+ if (str_len(*result) + 2 > max_var_size)
+ break;
+
+ str_append_c(*result, '\\');
+ } else if ((str_len(*result) + (p - poff) + n) > max_var_size) {
+ break;
+ }
+ if (p + n > pend) {
+ p = pend;
+ break;
+ }
+ p += n;
+ }
+
+ str_append_data(*result, poff, p - poff);
+
+ return TRUE;
+}
+
+/*
+ * Modifier argument
+ */
+
+/* [MODIFIER]:
+ * ":lower" / ":upper" / ":lowerfirst" / ":upperfirst" /
+ * ":quotewildcard" / ":length"
+ */
+
+/* Forward declarations */
+
+static bool tag_modifier_is_instance_of
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **context);
+
+/* Modifier tag object */
+
+static const struct sieve_argument_def modifier_tag = {
+ .identifier = "MODIFIER",
+ .flags = SIEVE_ARGUMENT_FLAG_MULTIPLE,
+ .is_instance_of = tag_modifier_is_instance_of
+};
+
+/* Modifier tag implementation */
+
+static bool tag_modifier_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data)
+{
+ const struct sieve_variables_modifier *modf;
+
+ if ( data == NULL ) {
+ return ext_variables_modifier_exists(ext, valdtr, identifier);
+ }
+
+ if ( (modf=ext_variables_modifier_create_instance
+ (ext, valdtr, cmd, identifier)) == NULL )
+ return FALSE;
+
+ *data = (void *) modf;
+
+ return TRUE;
+}
+
+/* Registration */
+
+void sieve_variables_modifiers_link_tag
+(struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, var_ext, &modifier_tag, 0);
+}
+
+/* Validation */
+
+bool sieve_variables_modifiers_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+ struct sieve_ast_argument *arg;
+
+ arg = sieve_command_first_argument(cmd);
+ while ( arg != NULL && arg != cmd->first_positional ) {
+ const struct sieve_variables_modifier *modfs;
+ const struct sieve_variables_modifier *modf;
+ unsigned int i, modf_count;
+ bool inserted;
+
+ if ( !sieve_argument_is(arg, modifier_tag) ) {
+ arg = sieve_ast_argument_next(arg);
+ continue;
+ }
+ modf = (const struct sieve_variables_modifier *)
+ arg->argument->data;
+
+ inserted = FALSE;
+ modfs = array_get(modifiers, &modf_count);
+ for ( i = 0; i < modf_count && !inserted; i++ ) {
+
+ if ( modfs[i].def->precedence == modf->def->precedence ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "modifiers :%s and :%s specified for the set command conflict "
+ "having equal precedence",
+ modfs[i].def->obj_def.identifier, modf->def->obj_def.identifier);
+ return FALSE;
+ }
+
+ if ( modfs[i].def->precedence < modf->def->precedence ) {
+ array_insert(modifiers, i, modf, 1);
+ inserted = TRUE;
+ }
+ }
+
+ if ( !inserted )
+ array_append(modifiers, modf, 1);
+
+ /* Added to modifier list;
+ self-destruct to prevent implicit code generation */
+ arg = sieve_ast_arguments_detach(arg, 1);
+ }
+ return TRUE;
+}
+
+bool sieve_variables_modifiers_generate
+(const struct sieve_codegen_env *cgenv,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ const struct sieve_variables_modifier *modfs;
+ unsigned int i, modf_count;
+
+ sieve_binary_emit_byte(sblock, array_count(modifiers));
+
+ modfs = array_get(modifiers, &modf_count);
+ for ( i = 0; i < modf_count; i++ ) {
+ ext_variables_opr_modifier_emit(sblock,
+ modfs[i].object.ext, modfs[i].def);
+ }
+ return TRUE;
+}
+
+/*
+ * Modifier coding
+ */
+
+const struct sieve_operand_class sieve_variables_modifier_operand_class =
+ { "modifier" };
+
+static const struct sieve_extension_objects core_modifiers =
+ SIEVE_VARIABLES_DEFINE_MODIFIERS(ext_variables_core_modifiers);
+
+const struct sieve_operand_def modifier_operand = {
+ .name = "modifier",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERAND_MODIFIER,
+ .class = &sieve_variables_modifier_operand_class,
+ .interface = &core_modifiers
+};
+
+bool sieve_variables_modifiers_code_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ unsigned int mdfs, i;
+
+ /* Read the number of applied modifiers we need to read */
+ if ( !sieve_binary_read_byte(denv->sblock, address, &mdfs) )
+ return FALSE;
+
+ /* Print all modifiers (sorted during code generation already) */
+ for ( i = 0; i < mdfs; i++ ) {
+ if ( !ext_variables_opr_modifier_dump(denv, address) )
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int sieve_variables_modifiers_code_read(
+ const struct sieve_runtime_env *renv,
+ const struct sieve_extension *var_ext, sieve_size_t *address,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers)
+{
+ unsigned int lprec, mdfs, i;
+
+ if ( !sieve_binary_read_byte(renv->sblock, address, &mdfs) ) {
+ sieve_runtime_trace_error(renv, "invalid modifier count");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ t_array_init(modifiers, mdfs);
+
+ lprec = (unsigned int)-1;
+ for ( i = 0; i < mdfs; i++ ) {
+ struct sieve_variables_modifier modf;
+
+ if ( !ext_variables_opr_modifier_read(renv, var_ext,
+ address, &modf) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+ if ( modf.def != NULL ) {
+ if ( modf.def->precedence >= lprec ) {
+ sieve_runtime_trace_error(renv,
+ "unsorted modifier precedence");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ lprec = modf.def->precedence;
+ }
+
+ array_append(modifiers, &modf, 1);
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Modifier application
+ */
+
+int sieve_variables_modifiers_apply
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *var_ext,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers,
+ string_t **value)
+{
+ const struct ext_variables_config *config =
+ ext_variables_get_config(var_ext);
+ const struct sieve_variables_modifier *modfs;
+ unsigned int i, modf_count;
+
+ /* Hold value within limits */
+ if ( str_len(*value) > config->max_variable_size ) {
+ /* assume variable originates from code, so copy it first */
+ string_t *new_value = t_str_new(config->max_variable_size+3);
+ str_append_str(new_value, *value);
+ *value = new_value;
+ str_truncate_utf8(*value, config->max_variable_size);
+ }
+
+ if ( !array_is_created(modifiers) )
+ return SIEVE_EXEC_OK;
+
+ modfs = array_get(modifiers, &modf_count);
+ if ( modf_count == 0 )
+ return SIEVE_EXEC_OK;
+
+ for ( i = 0; i < modf_count; i++ ) {
+ string_t *new_value;
+ const struct sieve_variables_modifier *modf = &modfs[i];
+
+ if ( modf->def != NULL && modf->def->modify != NULL ) {
+ if ( !modf->def->modify(modf, *value, &new_value) )
+ return SIEVE_EXEC_FAILURE;
+
+ *value = new_value;
+ if ( *value == NULL )
+ return SIEVE_EXEC_FAILURE;
+
+ sieve_runtime_trace_here
+ (renv, SIEVE_TRLVL_COMMANDS,
+ "modify :%s \"%s\" => \"%s\"",
+ sieve_variables_modifier_name(modf),
+ str_sanitize(str_c(*value), 256),
+ str_sanitize(str_c(new_value), 256));
+
+ /* Hold value within limits */
+ if ( str_len(*value) > config->max_variable_size )
+ str_truncate_utf8(*value, config->max_variable_size);
+ }
+ }
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.h
new file mode 100644
index 0000000..50b4256
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-modifiers.h
@@ -0,0 +1,66 @@
+#ifndef EXT_VARIABLES_MODIFIERS_H
+#define EXT_VARIABLES_MODIFIERS_H
+
+#include "sieve-common.h"
+#include "sieve-runtime-trace.h"
+
+#include "ext-variables-common.h"
+
+#define ext_variables_namespace_name(nspc) \
+ (nspc)->object->def->name
+#define ext_variables_namespaces_equal(nspc1, nspc2) \
+ ( (nspc1)->def == (nspc2)->def ))
+
+/*
+ * Modifier registry
+ */
+
+bool ext_variables_modifier_exists
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const char *identifier);
+const struct sieve_variables_modifier *ext_variables_modifier_create_instance
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *identifier);
+
+void ext_variables_register_core_modifiers
+ (const struct sieve_extension *var_ext,
+ struct ext_variables_validator_context *ctx);
+
+/*
+ * Modifier operand
+ */
+
+extern const struct sieve_operand_def modifier_operand;
+
+static inline void ext_variables_opr_modifier_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_variables_modifier_def *modf_def)
+{
+ sieve_opr_object_emit(sblock, ext, &modf_def->obj_def);
+}
+
+static inline bool
+ext_variables_opr_modifier_read(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *var_ext,
+ sieve_size_t *address,
+ struct sieve_variables_modifier *modf)
+{
+ if ( !sieve_opr_object_read
+ (renv, &sieve_variables_modifier_operand_class, address, &modf->object) ) {
+ sieve_runtime_trace_error(renv, "invalid modifier operand");
+ return FALSE;
+ }
+
+ modf->def = (const struct sieve_variables_modifier_def *) modf->object.def;
+ modf->var_ext = var_ext;
+ return TRUE;
+}
+
+static inline bool ext_variables_opr_modifier_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ return sieve_opr_object_dump
+ (denv, &sieve_variables_modifier_operand_class, address, NULL);
+}
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.c
new file mode 100644
index 0000000..fac0b71
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+
+#include <ctype.h>
+
+bool sieve_variable_identifier_is_valid(const char *identifier)
+{
+ const char *p = identifier;
+ size_t plen = strlen(identifier);
+ const char *pend;
+
+ if ( plen == 0 || plen >= EXT_VARIABLES_MAX_VARIABLE_NAME_LEN )
+ return FALSE;
+
+ pend = PTR_OFFSET(identifier, plen);
+
+ if ( *p == '_' || i_isalpha(*p) ) {
+ p++;
+
+ while ( p < pend && (*p == '_' || i_isalnum(*p)) ) {
+ p++;
+ }
+ }
+
+ return ( p == pend );
+}
+
+int ext_variable_name_parse
+(ARRAY_TYPE(sieve_variable_name) *vname, const char **str, const char *strend)
+{
+ const char *p = *str;
+
+ array_clear(vname);
+
+ while ( p < strend ) {
+ struct sieve_variable_name *cur_element;
+ string_t *cur_ident;
+
+ /* Acquire current position in the array */
+
+ if ( array_count(vname) >= EXT_VARIABLES_MAX_NAMESPACE_ELEMENTS )
+ return -1;
+
+ cur_element = array_append_space(vname);
+ cur_ident = cur_element->identifier = t_str_new(32);
+
+ /* Parse element */
+
+ /* Identifier */
+ if ( *p == '_' || i_isalpha(*p) ) {
+ cur_element->num_variable = -1;
+ str_truncate(cur_ident, 0);
+ str_append_c(cur_ident, *p);
+ p++;
+
+ while ( p < strend && (*p == '_' || i_isalnum(*p)) ) {
+ if ( str_len(cur_ident) >= EXT_VARIABLES_MAX_VARIABLE_NAME_LEN )
+ return -1;
+ str_append_c(cur_ident, *p);
+ p++;
+ }
+
+ /* Num-variable */
+ } else if ( i_isdigit(*p) ) {
+ cur_element->num_variable = *p - '0';
+ p++;
+
+ while ( p < strend && i_isdigit(*p) ) {
+ cur_element->num_variable = cur_element->num_variable*10 + (*p - '0');
+ p++;
+ }
+
+ /* If a num-variable is first, no more elements can follow because no
+ * namespace is specified.
+ */
+ if ( array_count(vname) == 1 ) {
+ *str = p;
+ return 1;
+ }
+ } else {
+ *str = p;
+ return -1;
+ }
+
+ /* Check whether next name element is present */
+ if ( p < strend && *p == '.' ) {
+ p++;
+
+ /* It may not be empty */
+ if ( p >= strend )
+ return -1;
+ } else
+ break;
+ }
+
+ *str = p;
+ return array_count(vname);
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.h
new file mode 100644
index 0000000..289a978
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-name.h
@@ -0,0 +1,43 @@
+#ifndef EXT_VARIABLES_NAME_H
+#define EXT_VARIABLES_NAME_H
+
+/* Variable Substitution
+ * ---------------------
+ *
+ * The variable strings are preprocessed into an AST list consisting of variable
+ * substitutions and constant parts of the string. The variables to which
+ * the substitutions link are looked up and their index in their scope storage
+ * is what is added to the list and eventually emitted as byte code. So, in
+ * bytecode a variable string will look as a series of substrings interrupted by
+ * integer operands that refer to variables. During execution, the strings and
+ * the looked-up variables are concatenated to obtain the desired result. The
+ * the variable references are simple indexes into an array of variables, so
+ * looking these up during execution is a trivial process.
+ *
+ * However (RFC 5229):
+ * Tests or actions in future extensions may need to access the
+ * unexpanded version of the string argument and, e.g., do the expansion
+ * after setting variables in its namespace. The design of the
+ * implementation should allow this.
+ *
+ * Various options exist to provide this feature. If the extension is entirely
+ * namespace-based there is actually not very much of a problem. The variable
+ * list can easily be extended with new argument-types that refer to a variable
+ * identifier instead of an index in the variable's storage.
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * Variable name parsing
+ */
+
+int ext_variable_name_parse
+ (ARRAY_TYPE(sieve_variable_name) *vname, const char **str, const char *strend);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
new file mode 100644
index 0000000..4df62e2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.c
@@ -0,0 +1,236 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-namespaces.h"
+
+#include <ctype.h>
+
+/*
+ * Namespace registry
+ */
+
+void sieve_variables_namespace_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_namespace_def *nspc_def)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+
+ sieve_validator_object_registry_add(ctx->namespaces, ext, &nspc_def->obj_def);
+}
+
+bool ext_variables_namespace_exists
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const char *identifier)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+
+ return sieve_validator_object_registry_find
+ (ctx->namespaces, identifier, NULL);
+}
+
+const struct sieve_variables_namespace *ext_variables_namespace_create_instance
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *identifier)
+{
+ struct ext_variables_validator_context *ctx =
+ ext_variables_validator_context_get(var_ext, valdtr);
+ struct sieve_object object;
+ struct sieve_variables_namespace *nspc;
+ pool_t pool;
+
+ if ( !sieve_validator_object_registry_find
+ (ctx->namespaces, identifier, &object) )
+ return NULL;
+
+ pool = sieve_command_pool(cmd);
+ nspc = p_new(pool, struct sieve_variables_namespace, 1);
+ nspc->object = object;
+ nspc->def = (const struct sieve_variables_namespace_def *) object.def;
+
+ return nspc;
+}
+
+/*
+ * Namespace variable argument
+ */
+
+struct arg_namespace_variable {
+ const struct sieve_variables_namespace *namespace;
+
+ void *data;
+};
+
+static bool arg_namespace_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED);
+
+const struct sieve_argument_def namespace_argument = {
+ .identifier = "@namespace",
+ .generate = arg_namespace_generate
+};
+
+bool ext_variables_namespace_argument_activate
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, bool assignment)
+{
+ pool_t pool = sieve_command_pool(cmd);
+ struct sieve_ast *ast = arg->ast;
+ const struct sieve_variables_namespace *nspc;
+ struct arg_namespace_variable *var;
+ const struct sieve_variable_name *name_element = array_idx(var_name, 0);
+ void *var_data = NULL;
+
+ nspc = ext_variables_namespace_create_instance
+ (this_ext, valdtr, cmd, str_c(name_element->identifier));
+ if ( nspc == NULL ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "referring to variable in unknown namespace '%s'",
+ str_c(name_element->identifier));
+ return FALSE;
+ }
+
+ if ( nspc->def != NULL && nspc->def->validate != NULL &&
+ !nspc->def->validate
+ (valdtr, nspc, arg, cmd, var_name, &var_data, assignment) ) {
+ return FALSE;
+ }
+
+ var = p_new(pool, struct arg_namespace_variable, 1);
+ var->namespace = nspc;
+ var->data = var_data;
+
+ arg->argument = sieve_argument_create(ast, &namespace_argument, this_ext, 0);
+ arg->argument->data = (void *) var;
+
+ return TRUE;
+}
+
+struct sieve_ast_argument *ext_variables_namespace_argument_create
+(const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_ast_argument *parent_arg,
+ struct sieve_command *cmd, ARRAY_TYPE(sieve_variable_name) *var_name)
+{
+ struct sieve_ast *ast = parent_arg->ast;
+ struct sieve_ast_argument *new_arg;
+
+ new_arg = sieve_ast_argument_create(ast, sieve_ast_argument_line(parent_arg));
+ new_arg->type = SAAT_STRING;
+
+ if ( !ext_variables_namespace_argument_activate
+ (this_ext, valdtr, new_arg, cmd, var_name, FALSE) )
+ return NULL;
+
+ return new_arg;
+}
+
+static bool arg_namespace_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_argument *argument = arg->argument;
+ struct arg_namespace_variable *var =
+ (struct arg_namespace_variable *) argument->data;
+ const struct sieve_variables_namespace *nspc = var->namespace;
+
+ if ( nspc->def != NULL && nspc->def->generate != NULL )
+ return nspc->def->generate(cgenv, nspc, arg, cmd, var->data);
+
+ return TRUE;
+}
+
+/*
+ * Namespace variable operands
+ */
+
+const struct sieve_operand_class sieve_variables_namespace_operand_class =
+ { "variable-namespace" };
+
+static bool opr_namespace_variable_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_namespace_variable_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_opr_string_interface namespace_variable_interface = {
+ opr_namespace_variable_dump,
+ opr_namespace_variable_read
+};
+
+const struct sieve_operand_def namespace_variable_operand = {
+ .name = "namespace",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERAND_NAMESPACE_VARIABLE,
+ .class = &string_class,
+ .interface = &namespace_variable_interface
+};
+
+void sieve_variables_opr_namespace_variable_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_namespace_def *nspc_def)
+{
+ i_assert( sieve_extension_is(var_ext, variables_extension) );
+ sieve_operand_emit(sblock, var_ext, &namespace_variable_operand);
+ sieve_opr_object_emit(sblock, ext, &nspc_def->obj_def);
+}
+
+static bool opr_namespace_variable_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ struct sieve_variables_namespace nspc;
+ struct sieve_operand nsoprnd;
+
+ if ( !sieve_operand_read(denv->sblock, address, NULL, &nsoprnd) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_opr_object_read_data
+ (denv->sblock, &nsoprnd, &sieve_variables_namespace_operand_class, address,
+ &nspc.object) ) {
+ return FALSE;
+ }
+
+ nspc.def = (const struct sieve_variables_namespace_def *) nspc.object.def;
+
+ if ( nspc.def == NULL || nspc.def->dump_variable == NULL )
+ return FALSE;
+
+ return nspc.def->dump_variable(denv, &nspc, oprnd, address);
+}
+
+static int opr_namespace_variable_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r)
+{
+ struct sieve_variables_namespace nspc;
+
+ if ( !sieve_opr_object_read
+ (renv, &sieve_variables_namespace_operand_class, address, &nspc.object) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable namespace operand corrupt: failed to read");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ nspc.def = (const struct sieve_variables_namespace_def *) nspc.object.def;
+
+ if ( nspc.def == NULL || nspc.def->read_variable == NULL )
+ return SIEVE_EXEC_FAILURE;
+
+ return nspc.def->read_variable(renv, &nspc, oprnd, address, str_r);
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.h
new file mode 100644
index 0000000..a687582
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-namespaces.h
@@ -0,0 +1,43 @@
+#ifndef EXT_VARIABLES_NAMESPACES_H
+#define EXT_VARIABLES_NAMESPACES_H
+
+#include "sieve-common.h"
+
+#include "ext-variables-common.h"
+#include "sieve-ext-variables.h"
+
+/*
+ * Namespace registry
+ */
+
+bool ext_variables_namespace_exists
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const char *identifier);
+const struct sieve_variables_namespace *ext_variables_namespace_create_instance
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *identifier);
+
+void ext_variables_register_core_namespaces
+ (const struct sieve_extension *var_ext,
+ struct ext_variables_validator_context *ctx);
+
+/*
+ * Namespace argument
+ */
+
+struct sieve_ast_argument *ext_variables_namespace_argument_create
+ (const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_ast_argument *parent_arg,
+ struct sieve_command *cmd, ARRAY_TYPE(sieve_variable_name) *var_name);
+bool ext_variables_namespace_argument_activate
+ (const struct sieve_extension *this_ext, struct sieve_validator *valdtr,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, bool assignment);
+
+/*
+ * Namespace operand
+ */
+
+extern const struct sieve_operand_def namespace_variable_operand;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.c
new file mode 100644
index 0000000..359f3bf
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.c
@@ -0,0 +1,279 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-match-types.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-limits.h"
+#include "ext-variables-name.h"
+#include "ext-variables-dump.h"
+#include "ext-variables-operands.h"
+
+/*
+ * Variable operand
+ */
+
+static bool opr_variable_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_variable_read_value
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface variable_interface = {
+ opr_variable_dump,
+ opr_variable_read_value
+};
+
+const struct sieve_operand_def variable_operand = {
+ .name = "variable",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERAND_VARIABLE,
+ .class = &string_class,
+ .interface = &variable_interface
+};
+
+void sieve_variables_opr_variable_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ struct sieve_variable *var)
+{
+ i_assert( sieve_extension_is(var_ext, variables_extension) );
+
+ if ( var->ext == NULL ) {
+ /* Default variable storage */
+ (void) sieve_operand_emit(sblock, var_ext, &variable_operand);
+ (void) sieve_binary_emit_byte(sblock, 0); /* Default */
+ (void) sieve_binary_emit_unsigned(sblock, var->index);
+ return;
+ }
+
+ (void) sieve_operand_emit(sblock, var_ext, &variable_operand);
+ (void) sieve_binary_emit_extension(sblock, var->ext, 1); /* Extension */
+ (void) sieve_binary_emit_unsigned(sblock, var->index);
+}
+
+static bool opr_variable_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = oprnd->ext;
+ unsigned int index = 0;
+ const struct sieve_extension *ext;
+ unsigned int code = 1; /* Initially set to offset value */
+ const char *identifier;
+
+ if ( !sieve_binary_read_extension(denv->sblock, address, &code, &ext) )
+ return FALSE;
+
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &index) )
+ return FALSE;
+
+ identifier = ext_variables_dump_get_identifier(this_ext, denv, ext, index);
+ identifier = identifier == NULL ? "??" : identifier;
+
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: VAR[%s] ${%s}",
+ oprnd->field_name, sieve_ext_variables_get_varid(ext, index), identifier);
+ else
+ sieve_code_dumpf(denv, "VAR[%s] ${%s}",
+ sieve_ext_variables_get_varid(ext, index), identifier);
+
+ return TRUE;
+}
+
+static int opr_variable_read_value
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r)
+{
+ const struct sieve_extension *this_ext = oprnd->ext;
+ const struct sieve_extension *ext;
+ unsigned int code = 1; /* Initially set to offset value */
+ struct sieve_variable_storage *storage;
+ unsigned int index = 0;
+
+ if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: invalid extension byte");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ storage = sieve_ext_variables_runtime_get_storage
+ (this_ext, renv, ext);
+ if ( storage == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: extension has no storage");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+ /* Parameter str can be NULL if we are requested to only skip and not
+ * actually read the argument.
+ */
+ if ( str_r != NULL ) {
+ if ( !sieve_variable_get(storage, index, str_r) )
+ return SIEVE_EXEC_FAILURE;
+
+ if ( *str_r == NULL ) *str_r = t_str_new(0);
+ }
+
+ return SIEVE_EXEC_OK;
+ }
+
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: invalid variable index");
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_variable_operand_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name,
+ struct sieve_variable_storage **storage_r, unsigned int *var_index_r)
+{
+ const struct sieve_extension *ext;
+ unsigned int code = 1; /* Initially set to offset value */
+ unsigned int idx = 0;
+
+ oprnd->field_name = field_name;
+
+ if ( !sieve_operand_is_variable(oprnd) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "expected variable operand but found %s", sieve_operand_name(oprnd));
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( !sieve_binary_read_extension(renv->sblock, address, &code, &ext) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: invalid extension byte");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ *storage_r = sieve_ext_variables_runtime_get_storage
+ (oprnd->ext, renv, ext);
+ if ( *storage_r == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: extension has no storage");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &idx) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "variable operand corrupt: invalid variable index");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ *var_index_r = idx;
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_variable_operand_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, struct sieve_variable_storage **storage_r,
+ unsigned int *var_index_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0)
+ return ret;
+
+ return sieve_variable_operand_read_data
+ (renv, &operand, address, field_name, storage_r, var_index_r);
+}
+
+/*
+ * Match value operand
+ */
+
+static bool opr_match_value_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_match_value_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface match_value_interface = {
+ opr_match_value_dump,
+ opr_match_value_read
+};
+
+const struct sieve_operand_def match_value_operand = {
+ .name = "match-value",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERAND_MATCH_VALUE,
+ .class = &string_class,
+ .interface = &match_value_interface
+};
+
+void sieve_variables_opr_match_value_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ unsigned int index)
+{
+ i_assert( sieve_extension_is(var_ext, variables_extension) );
+ (void) sieve_operand_emit(sblock, var_ext, &match_value_operand);
+ (void) sieve_binary_emit_unsigned(sblock, index);
+}
+
+static bool opr_match_value_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ unsigned int index = 0;
+
+ if (sieve_binary_read_unsigned(denv->sblock, address, &index) ) {
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf
+ (denv, "%s: MATCHVAL %lu", oprnd->field_name, (unsigned long) index);
+ else
+ sieve_code_dumpf(denv, "MATCHVAL %lu", (unsigned long) index);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int opr_match_value_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r)
+{
+ const struct sieve_extension *this_ext = oprnd->ext;
+ const struct ext_variables_config *config =
+ ext_variables_get_config(this_ext);
+ unsigned int index = 0;
+
+ if ( sieve_binary_read_unsigned(renv->sblock, address, &index) ) {
+ /* Parameter str can be NULL if we are requested to only skip and not
+ * actually read the argument.
+ */
+ if ( str_r != NULL ) {
+ sieve_match_values_get(renv, index, str_r);
+
+ if ( *str_r == NULL )
+ *str_r = t_str_new(0);
+ else if ( str_len(*str_r) > config->max_variable_size )
+ str_truncate_utf8(*str_r, config->max_variable_size);
+ }
+
+ return SIEVE_EXEC_OK;
+ }
+
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "match value operand corrupt: invalid index data");
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.h b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.h
new file mode 100644
index 0000000..64a7781
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables-operands.h
@@ -0,0 +1,37 @@
+#ifndef EXT_VARIABLES_OPERANDS_H
+#define EXT_VARIABLES_OPERANDS_H
+
+#include "lib.h"
+#include "hash.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "ext-variables-common.h"
+
+/*
+ * Variable operand
+ */
+
+extern const struct sieve_operand_def variable_operand;
+
+bool ext_variables_opr_variable_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_variable_storage **storage, unsigned int *var_index);
+
+/*
+ * Match value operand
+ */
+
+extern const struct sieve_operand_def match_value_operand;
+
+/*
+ * Variable string operand
+ */
+
+void ext_variables_opr_variable_string_emit
+ (struct sieve_binary *sbin, unsigned int elements);
+
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/ext-variables.c b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables.c
new file mode 100644
index 0000000..6bdef58
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/ext-variables.c
@@ -0,0 +1,84 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension variables
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: RFC 5229
+ * Implementation: full
+ * Status: testing
+ *
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-validator.h"
+
+#include "ext-variables-common.h"
+#include "ext-variables-arguments.h"
+#include "ext-variables-operands.h"
+#include "ext-variables-namespaces.h"
+#include "ext-variables-modifiers.h"
+#include "ext-variables-dump.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *ext_variables_operations[] = {
+ &cmd_set_operation,
+ &tst_string_operation
+};
+
+/*
+ * Operands
+ */
+
+const struct sieve_operand_def *ext_variables_operands[] = {
+ &variable_operand,
+ &match_value_operand,
+ &namespace_variable_operand,
+ &modifier_operand
+};
+
+/*
+ * Extension
+ */
+
+static bool ext_variables_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+
+const struct sieve_extension_def variables_extension = {
+ .name = "variables",
+ .load = ext_variables_load,
+ .unload = ext_variables_unload,
+ .validator_load = ext_variables_validator_load,
+ .generator_load = ext_variables_generator_load,
+ .interpreter_load = ext_variables_interpreter_load,
+ .code_dump = ext_variables_code_dump,
+ SIEVE_EXT_DEFINE_OPERATIONS(ext_variables_operations),
+ SIEVE_EXT_DEFINE_OPERANDS(ext_variables_operands)
+};
+
+static bool ext_variables_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ sieve_validator_argument_override
+ (validator, SAT_VAR_STRING, ext, &variable_string_argument);
+
+ sieve_validator_register_command(validator, ext, &cmd_set);
+ sieve_validator_register_command(validator, ext, &tst_string);
+
+ ext_variables_validator_initialize(ext, validator);
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/sieve-ext-variables.h b/pigeonhole/src/lib-sieve/plugins/variables/sieve-ext-variables.h
new file mode 100644
index 0000000..ce0d0bc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/sieve-ext-variables.h
@@ -0,0 +1,364 @@
+#ifndef SIEVE_EXT_VARIABLES_H
+#define SIEVE_EXT_VARIABLES_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-objects.h"
+#include "sieve-code.h"
+
+/* Public interface for other extensions to use
+ */
+
+/*
+ * Limits
+ */
+
+unsigned int
+sieve_variables_get_max_scope_size(const struct sieve_extension *var_ext);
+size_t
+sieve_variables_get_max_variable_size(const struct sieve_extension *var_ext);
+
+/*
+ * Variable extension
+ */
+
+/* FIXME: this is not suitable for future plugin support */
+
+extern const struct sieve_extension_def variables_extension;
+
+static inline const struct sieve_extension *sieve_ext_variables_get_extension
+(struct sieve_instance *svinst)
+{
+ return sieve_extension_register(svinst, &variables_extension, FALSE);
+}
+
+/*
+ * Variable name
+ */
+
+struct sieve_variable_name {
+ string_t *identifier;
+ int num_variable;
+};
+
+ARRAY_DEFINE_TYPE(sieve_variable_name, struct sieve_variable_name);
+
+bool sieve_variable_identifier_is_valid(const char *identifier);
+
+/*
+ * Variable scope
+ */
+
+struct sieve_variable {
+ const char *identifier;
+ unsigned int index;
+
+ const struct sieve_extension *ext;
+ void *context;
+};
+
+struct sieve_variable_scope;
+
+struct sieve_variable_scope *sieve_variable_scope_create
+ (struct sieve_instance *svinst, const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext);
+void sieve_variable_scope_ref
+ (struct sieve_variable_scope *scope);
+void sieve_variable_scope_unref
+ (struct sieve_variable_scope **scope);
+pool_t sieve_variable_scope_pool
+ (struct sieve_variable_scope *scope);
+
+struct sieve_variable *sieve_variable_scope_declare
+ (struct sieve_variable_scope *scope, const char *identifier);
+struct sieve_variable *sieve_variable_scope_import
+ (struct sieve_variable_scope *scope, struct sieve_variable *var);
+struct sieve_variable *sieve_variable_scope_get_variable
+ (struct sieve_variable_scope *scope, const char *identifier);
+struct sieve_variable *sieve_variable_scope_get_indexed
+ (struct sieve_variable_scope *scope, unsigned int index);
+
+/* Binary */
+
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_create
+ (struct sieve_variable_scope *scope);
+
+void sieve_variable_scope_binary_ref
+ (struct sieve_variable_scope_binary *scpbin);
+void sieve_variable_scope_binary_unref
+ (struct sieve_variable_scope_binary **scpbin);
+
+struct sieve_variable_scope *sieve_variable_scope_binary_dump
+ (struct sieve_instance *svinst,
+ const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address);
+struct sieve_variable_scope_binary *sieve_variable_scope_binary_read
+ (struct sieve_instance *svinst,
+ const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ struct sieve_binary_block *sblock, sieve_size_t *address);
+
+struct sieve_variable_scope *sieve_variable_scope_binary_get
+ (struct sieve_variable_scope_binary *scpbin);
+unsigned int sieve_variable_scope_binary_get_size
+ (struct sieve_variable_scope_binary *scpbin);
+
+/*
+ * Variable namespaces
+ */
+
+struct sieve_variables_namespace;
+
+struct sieve_variables_namespace_def {
+ struct sieve_object_def obj_def;
+
+ bool (*validate)
+ (struct sieve_validator *valdtr,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment);
+ bool (*generate)
+ (const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ void *var_data);
+
+ bool (*dump_variable)
+ (const struct sieve_dumptime_env *denv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address);
+ int (*read_variable)
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address, string_t **str);
+};
+
+#define SIEVE_VARIABLES_DEFINE_NAMESPACE(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_VARIABLES_DEFINE_NAMESPACES(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+struct sieve_variables_namespace {
+ struct sieve_object object;
+
+ const struct sieve_variables_namespace_def *def;
+};
+
+void sieve_variables_namespace_register
+(const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_namespace_def *nspc_def);
+
+extern const struct sieve_operand_class sieve_variables_namespace_operand_class;
+
+void sieve_variables_opr_namespace_variable_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_namespace_def *nspc_def);
+
+/* Iteration over all declared variables */
+
+struct sieve_variable_scope_iter;
+
+struct sieve_variable_scope_iter *sieve_variable_scope_iterate_init
+ (struct sieve_variable_scope *scope);
+bool sieve_variable_scope_iterate
+ (struct sieve_variable_scope_iter *iter, struct sieve_variable **var_r);
+void sieve_variable_scope_iterate_deinit
+ (struct sieve_variable_scope_iter **iter);
+
+/* Statistics */
+
+unsigned int sieve_variable_scope_declarations
+ (struct sieve_variable_scope *scope);
+unsigned int sieve_variable_scope_size
+ (struct sieve_variable_scope *scope);
+
+/* Get all native variables */
+
+struct sieve_variable * const *sieve_variable_scope_get_variables
+ (struct sieve_variable_scope *scope, unsigned int *size_r);
+
+/*
+ * Variable storage
+ */
+
+struct sieve_variable_storage;
+
+struct sieve_variable_storage *sieve_variable_storage_create
+ (const struct sieve_extension *var_ext, pool_t pool,
+ struct sieve_variable_scope_binary *scpbin);
+bool sieve_variable_get
+ (struct sieve_variable_storage *storage, unsigned int index,
+ string_t **value);
+bool sieve_variable_get_modifiable
+ (struct sieve_variable_storage *storage, unsigned int index,
+ string_t **value);
+bool sieve_variable_assign
+ (struct sieve_variable_storage *storage, unsigned int index,
+ const string_t *value);
+bool sieve_variable_assign_cstr
+ (struct sieve_variable_storage *storage, unsigned int index,
+ const char *value);
+bool sieve_variable_get_identifier
+ (struct sieve_variable_storage *storage, unsigned int index,
+ const char **identifier);
+const char *sieve_variable_get_varid
+ (struct sieve_variable_storage *storage, unsigned int index);
+
+/*
+ * Variables access
+ */
+
+bool sieve_ext_variables_is_active
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr);
+
+struct sieve_variable_scope *sieve_ext_variables_get_local_scope
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr);
+
+/* Runtime */
+
+static inline const char *sieve_ext_variables_get_varid
+(const struct sieve_extension *ext, unsigned int index)
+{
+ if ( ext == NULL )
+ return t_strdup_printf("%ld", (long) index);
+
+ return t_strdup_printf("%s:%ld", sieve_extension_name(ext), (long) index);
+}
+
+struct sieve_variable_storage *sieve_ext_variables_runtime_get_storage
+ (const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext);
+void sieve_ext_variables_runtime_set_storage
+ (const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext, struct sieve_variable_storage *storage);
+
+const char *sieve_ext_variables_runtime_get_identifier
+(const struct sieve_extension *var_ext, const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext, unsigned int index);
+
+/*
+ * Variable arguments
+ */
+
+bool sieve_variable_argument_activate
+ (const struct sieve_extension *var_ext,
+ const struct sieve_extension *this_ext,
+ struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, bool assignment);
+
+/*
+ * Variable operands
+ */
+
+extern const struct sieve_operand_def variable_operand;
+
+void sieve_variables_opr_variable_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ struct sieve_variable *var);
+void sieve_variables_opr_match_value_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *var_ext,
+ unsigned int index);
+
+int sieve_variable_operand_read_data
+ (const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name,
+ struct sieve_variable_storage **storage_r, unsigned int *var_index_r);
+int sieve_variable_operand_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, struct sieve_variable_storage **storage_r,
+ unsigned int *var_index_r);
+
+static inline bool sieve_operand_is_variable
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ operand->def == &variable_operand );
+}
+
+/*
+ * Modifiers
+ */
+
+/* Definition */
+
+struct sieve_variables_modifier;
+
+struct sieve_variables_modifier_def {
+ struct sieve_object_def obj_def;
+
+ unsigned int precedence;
+
+ bool (*modify)(const struct sieve_variables_modifier *modf,
+ string_t *in, string_t **result);
+};
+
+struct sieve_variables_modifier {
+ struct sieve_object object;
+ const struct sieve_extension *var_ext;
+
+ const struct sieve_variables_modifier_def *def;
+};
+
+#define SIEVE_VARIABLES_DEFINE_MODIFIER(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_VARIABLES_DEFINE_MODIFIERS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+#define sieve_variables_modifier_name(smodf) \
+ ( (smodf)->object.def->identifier )
+
+ARRAY_DEFINE_TYPE(sieve_variables_modifier,
+ struct sieve_variables_modifier);
+
+/* Registry */
+
+void sieve_variables_modifier_register
+ (const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_variables_modifier_def *smodf);
+
+/* Tagged argument */
+
+void sieve_variables_modifiers_link_tag
+ (struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
+ struct sieve_command_registration *cmd_reg);
+
+bool sieve_variables_modifiers_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+bool sieve_variables_modifiers_generate
+ (const struct sieve_codegen_env *cgenv,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+/* Coding */
+
+extern const struct sieve_operand_class
+ sieve_variables_modifier_operand_class;
+
+bool sieve_variables_modifiers_code_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+int sieve_variables_modifiers_code_read(
+ const struct sieve_runtime_env *renv,
+ const struct sieve_extension *var_ext, sieve_size_t *address,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers);
+
+/* Application */
+
+int sieve_variables_modifiers_apply
+(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *var_ext,
+ ARRAY_TYPE(sieve_variables_modifier) *modifiers,
+ string_t **value);
+
+/*
+ * Code dumping
+ */
+
+void sieve_ext_variables_dump_set_scope
+(const struct sieve_extension *var_ext, const struct sieve_dumptime_env *denv,
+ const struct sieve_extension *ext, struct sieve_variable_scope *scope);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/variables/tst-string.c b/pigeonhole/src/lib-sieve/plugins/variables/tst-string.c
new file mode 100644
index 0000000..91b0b82
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/variables/tst-string.c
@@ -0,0 +1,271 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "ext-variables-common.h"
+
+/*
+ * String test
+ *
+ * Syntax:
+ * string [COMPARATOR] [MATCH-TYPE]
+ * <source: string-list> <key-list: string-list>
+ */
+
+static bool tst_string_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_string_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_string_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_string = {
+ .identifier = "string",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_string_registered,
+ .validate = tst_string_validate,
+ .generate = tst_string_generate
+};
+
+/*
+ * String operation
+ */
+
+static bool tst_string_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_string_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_string_operation = {
+ .mnemonic = "STRING",
+ .ext_def = &variables_extension,
+ .code = EXT_VARIABLES_OPERATION_STRING,
+ .dump = tst_string_operation_dump,
+ .execute = tst_string_operation_execute
+};
+
+/*
+ * Optional arguments
+ */
+
+enum tst_string_optional {
+ OPT_END,
+ OPT_COMPARATOR,
+ OPT_MATCH_TYPE
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_string_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, OPT_MATCH_TYPE);
+
+ return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool tst_string_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ const struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ const struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "source", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Test generation
+ */
+
+static bool tst_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_string_operation);
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_string_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "STRING-TEST");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ if ( sieve_match_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "source") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_string_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void tst_string_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int tst_string_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+
+struct tst_string_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_stringlist *value_list;
+};
+
+static struct sieve_stringlist *tst_string_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *value_list)
+{
+ struct tst_string_stringlist *strlist;
+
+ strlist = t_new(struct tst_string_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = tst_string_stringlist_next_item;
+ strlist->strlist.reset = tst_string_stringlist_reset;
+ strlist->strlist.get_length = tst_string_stringlist_get_length;
+ strlist->value_list = value_list;
+
+ return &strlist->strlist;
+}
+
+static int tst_string_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct tst_string_stringlist *strlist =
+ (struct tst_string_stringlist *)_strlist;
+
+ return sieve_stringlist_next_item(strlist->value_list, str_r);
+}
+
+static void tst_string_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct tst_string_stringlist *strlist =
+ (struct tst_string_stringlist *)_strlist;
+
+ sieve_stringlist_reset(strlist->value_list);
+}
+
+static int tst_string_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+ struct tst_string_stringlist *strlist =
+ (struct tst_string_stringlist *)_strlist;
+ string_t *item;
+ int length = 0;
+ int ret;
+
+ while ( (ret=sieve_stringlist_next_item(strlist->value_list, &item)) > 0 ) {
+ if ( str_len(item) > 0 )
+ length++;
+ }
+
+ return ( ret < 0 ? -1 : length );
+}
+
+static int tst_string_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_stringlist *source, *value_list, *key_list;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Handle match-type and comparator operands */
+ if ( sieve_match_opr_optional_read
+ (renv, address, NULL, &ret, &cmp, &mcht) < 0 )
+ return ret;
+
+ /* Read source */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "source", &source)) <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "string test");
+
+ /* Create wrapper string list wich does not count empty string items */
+ value_list = tst_string_stringlist_create(renv, source);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.am b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
new file mode 100644
index 0000000..f409e2b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = debug environment report
+
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in
new file mode 100644
index 0000000..43680f1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in
@@ -0,0 +1,692 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/vnd.dovecot
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = debug environment report
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.am b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.am
new file mode 100644
index 0000000..1e08946
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.am
@@ -0,0 +1,15 @@
+noinst_LTLIBRARIES = libsieve_ext_debug.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-debug-log.c
+
+libsieve_ext_debug_la_SOURCES = \
+ $(commands) \
+ ext-debug.c
+
+noinst_HEADERS = \
+ ext-debug-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in
new file mode 100644
index 0000000..f9b5450
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in
@@ -0,0 +1,687 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/vnd.dovecot/debug
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_debug_la_LIBADD =
+am__objects_1 = cmd-debug-log.lo
+am_libsieve_ext_debug_la_OBJECTS = $(am__objects_1) ext-debug.lo
+libsieve_ext_debug_la_OBJECTS = $(am_libsieve_ext_debug_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-debug-log.Plo \
+ ./$(DEPDIR)/ext-debug.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_debug_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_debug_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_debug.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-debug-log.c
+
+libsieve_ext_debug_la_SOURCES = \
+ $(commands) \
+ ext-debug.c
+
+noinst_HEADERS = \
+ ext-debug-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/debug/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/debug/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_debug.la: $(libsieve_ext_debug_la_OBJECTS) $(libsieve_ext_debug_la_DEPENDENCIES) $(EXTRA_libsieve_ext_debug_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_debug_la_OBJECTS) $(libsieve_ext_debug_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-debug-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-debug.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-debug-log.Plo
+ -rm -f ./$(DEPDIR)/ext-debug.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-debug-log.Plo
+ -rm -f ./$(DEPDIR)/ext-debug.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c
new file mode 100644
index 0000000..355daa5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/cmd-debug-log.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-debug-common.h"
+
+/*
+ * Debug_log command
+ *
+ * Syntax
+ * debug_log <message: string>
+ */
+
+static bool cmd_debug_log_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool cmd_debug_log_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def debug_log_command = {
+ .identifier = "debug_log",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_debug_log_validate,
+ .generate = cmd_debug_log_generate
+};
+
+/*
+ * Body operation
+ */
+
+static bool cmd_debug_log_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_debug_log_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def debug_log_operation = {
+ .mnemonic = "DEBUG_LOG",
+ .ext_def = &vnd_debug_extension,
+ .dump = cmd_debug_log_operation_dump,
+ .execute = cmd_debug_log_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_debug_log_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "message", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_debug_log_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &debug_log_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_debug_log_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "DEBUG_LOG");
+ sieve_code_descend(denv);
+
+ return sieve_opr_string_dump(denv, address, "key list");
+}
+
+/*
+ * Interpretation
+ */
+
+static int cmd_debug_log_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ string_t *message;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read message */
+
+ if ( (ret=sieve_opr_string_read(renv, address, "message", &message)) <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "debug_log \"%s\"",
+ str_sanitize(str_c(message), 80));
+
+ sieve_runtime_log(renv, NULL, "DEBUG: %s", str_c(message));
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h
new file mode 100644
index 0000000..88ae3db
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug-common.h
@@ -0,0 +1,22 @@
+#ifndef EXT_DEBUG_COMMON_H
+#define EXT_DEBUG_COMMON_H
+
+/*
+ * Extensions
+ */
+
+extern const struct sieve_extension_def vnd_debug_extension;
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def debug_log_command;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def debug_log_operation;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c
new file mode 100644
index 0000000..2781e83
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/ext-debug.c
@@ -0,0 +1,70 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension debug
+ * ---------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined; spec-bosch-sieve-debug
+ * Implementation: full
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-debug-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_debug_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+static bool ext_debug_interpreter_load
+ (const struct sieve_extension *ext ATTR_UNUSED,
+ const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED);
+
+
+const struct sieve_extension_def vnd_debug_extension = {
+ .name = "vnd.dovecot.debug",
+ .validator_load = ext_debug_validator_load,
+ .interpreter_load = ext_debug_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERATION(debug_log_operation),
+};
+
+static bool ext_debug_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *validator)
+{
+ /* Register new test */
+ sieve_validator_register_command(validator, ext, &debug_log_command);
+
+ return TRUE;
+}
+
+static bool ext_debug_interpreter_load
+(const struct sieve_extension *ext ATTR_UNUSED,
+ const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ if ( renv->ehandler != NULL ) {
+ sieve_error_handler_accept_infolog(renv->ehandler, TRUE);
+ }
+
+ return TRUE;
+}
+
+
+
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am
new file mode 100644
index 0000000..96618ff
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libsieve_ext_vnd_environment.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../environment \
+ -I$(srcdir)/../../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_vnd_environment_la_SOURCES = \
+ ext-vnd-environment.c \
+ ext-vnd-environment-items.c \
+ ext-vnd-environment-variables.c
+
+noinst_HEADERS = \
+ ext-vnd-environment-common.h
+
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in
new file mode 100644
index 0000000..b5d8b20
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in
@@ -0,0 +1,692 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/vnd.dovecot/environment
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_vnd_environment_la_LIBADD =
+am_libsieve_ext_vnd_environment_la_OBJECTS = ext-vnd-environment.lo \
+ ext-vnd-environment-items.lo ext-vnd-environment-variables.lo
+libsieve_ext_vnd_environment_la_OBJECTS = \
+ $(am_libsieve_ext_vnd_environment_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ext-vnd-environment-items.Plo \
+ ./$(DEPDIR)/ext-vnd-environment-variables.Plo \
+ ./$(DEPDIR)/ext-vnd-environment.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_vnd_environment_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_vnd_environment_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_vnd_environment.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../environment \
+ -I$(srcdir)/../../variables \
+ $(LIBDOVECOT_INCLUDE)
+
+libsieve_ext_vnd_environment_la_SOURCES = \
+ ext-vnd-environment.c \
+ ext-vnd-environment-items.c \
+ ext-vnd-environment-variables.c
+
+noinst_HEADERS = \
+ ext-vnd-environment-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/environment/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/environment/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_vnd_environment.la: $(libsieve_ext_vnd_environment_la_OBJECTS) $(libsieve_ext_vnd_environment_la_DEPENDENCIES) $(EXTRA_libsieve_ext_vnd_environment_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_vnd_environment_la_OBJECTS) $(libsieve_ext_vnd_environment_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vnd-environment-items.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vnd-environment-variables.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vnd-environment.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ext-vnd-environment-items.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-environment-variables.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-environment.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ext-vnd-environment-items.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-environment-variables.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-environment.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h
new file mode 100644
index 0000000..980c830
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-common.h
@@ -0,0 +1,37 @@
+#ifndef EXT_VND_ENVIRONMENT_COMMON_H
+#define EXT_VND_ENVIRONMENT_COMMON_H
+
+#include "sieve-ext-environment.h"
+
+/*
+ * Extension
+ */
+
+struct ext_vnd_environment_context {
+ const struct sieve_extension *env_ext;
+ const struct sieve_extension *var_ext;
+};
+
+extern const struct sieve_extension_def vnd_environment_extension;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def environment_namespace_operand;
+
+/*
+ * Environment items
+ */
+
+void ext_vnd_environment_items_register
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv);
+
+/*
+ * Variables
+ */
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c
new file mode 100644
index 0000000..af03255
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-items.c
@@ -0,0 +1,95 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Environment items
+ */
+
+/* default_mailbox */
+
+static const char *
+envit_default_mailbox_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ i_assert(eenv->scriptenv->default_mailbox != NULL);
+ return eenv->scriptenv->default_mailbox;
+}
+
+const struct sieve_environment_item default_mailbox_env_item = {
+ .name = "vnd.dovecot.default-mailbox",
+ .get_value = envit_default_mailbox_get_value
+};
+
+/* username */
+
+static const char *
+envit_username_get_value(const struct sieve_runtime_env *renv,
+ const char *name ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return eenv->svinst->username;
+}
+
+const struct sieve_environment_item username_env_item = {
+ .name = "vnd.dovecot.username",
+ .get_value = envit_username_get_value
+};
+
+/* config.* */
+
+static const char *
+envit_config_get_value(const struct sieve_runtime_env *renv, const char *name)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ if (*name == '\0')
+ return NULL;
+
+ return sieve_setting_get(eenv->svinst,
+ t_strconcat("sieve_env_", name, NULL));
+}
+
+const struct sieve_environment_item config_env_item = {
+ .name = "vnd.dovecot.config",
+ .prefix = TRUE,
+ .get_value = envit_config_get_value
+};
+
+/*
+ * Register
+ */
+
+void ext_vnd_environment_items_register(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv)
+{
+ struct ext_vnd_environment_context *ectx =
+ (struct ext_vnd_environment_context *)ext->context;
+
+ sieve_environment_item_register(ectx->env_ext, renv->interp,
+ &default_mailbox_env_item);
+ sieve_environment_item_register(ectx->env_ext, renv->interp,
+ &username_env_item);
+ sieve_environment_item_register(ectx->env_ext, renv->interp,
+ &config_env_item);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c
new file mode 100644
index 0000000..1466ee7
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment-variables.c
@@ -0,0 +1,206 @@
+/* Copyright (c) 2015-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+static bool vnspc_vnd_environment_validate
+ (struct sieve_validator *valdtr,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment);
+static bool vnspc_vnd_environment_generate
+ (const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd, void *var_data);
+static bool vnspc_vnd_environment_dump_variable
+ (const struct sieve_dumptime_env *denv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address);
+static int vnspc_vnd_environment_read_variable
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_variables_namespace_def
+environment_namespace = {
+ SIEVE_OBJECT("env", &environment_namespace_operand, 0),
+ .validate = vnspc_vnd_environment_validate,
+ .generate = vnspc_vnd_environment_generate,
+ .dump_variable = vnspc_vnd_environment_dump_variable,
+ .read_variable = vnspc_vnd_environment_read_variable
+};
+
+static bool vnspc_vnd_environment_validate
+(struct sieve_validator *valdtr,
+ const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd ATTR_UNUSED,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment)
+{
+ struct sieve_ast *ast = arg->ast;
+ const struct sieve_variable_name *name_elements;
+ unsigned int i, count;
+ const char *variable;
+ string_t *name;
+
+ /* Compose environment name from parsed variable name */
+ name = t_str_new(64);
+ name_elements = array_get(var_name, &count);
+ i_assert(count > 1);
+ for (i = 1; i < count; i++) {
+ if ( name_elements[i].num_variable >= 0 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "vnd.dovecot.environment: invalid variable name within "
+ "env namespace `env.%d': "
+ "encountered numeric variable name",
+ name_elements[i].num_variable);
+ return FALSE;
+ }
+ if (str_len(name) > 0)
+ str_append_c(name, '.');
+ str_append_str(name, name_elements[i].identifier);
+ }
+
+ variable = str_c(name);
+
+ if ( assignment ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "vnd.dovecot.environment: cannot assign to environment "
+ "variable `env.%s'", variable);
+ return FALSE;
+ }
+
+ *var_data = (void *) p_strdup(sieve_ast_pool(ast), variable);
+ return TRUE;
+}
+
+static bool vnspc_vnd_environment_generate
+(const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+ const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+ const char *variable = (const char *) var_data;
+ struct ext_vnd_environment_context *ext_data;
+
+ if ( this_ext == NULL )
+ return FALSE;
+
+ ext_data = (struct ext_vnd_environment_context *) this_ext->context;
+
+ sieve_variables_opr_namespace_variable_emit
+ (cgenv->sblock, ext_data->var_ext, this_ext, &environment_namespace);
+ sieve_binary_emit_cstring(cgenv->sblock, variable);
+
+ return TRUE;
+}
+
+static bool vnspc_vnd_environment_dump_variable
+(const struct sieve_dumptime_env *denv,
+ const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+ const struct sieve_operand *oprnd, sieve_size_t *address)
+{
+ string_t *var_name;
+
+ if ( !sieve_binary_read_string(denv->sblock, address, &var_name) )
+ return FALSE;
+
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: VAR ${env.%s}",
+ oprnd->field_name, str_c(var_name));
+ else
+ sieve_code_dumpf(denv, "VAR ${env.%s}",
+ str_c(var_name));
+
+ return TRUE;
+}
+
+static int vnspc_vnd_environment_read_variable
+(const struct sieve_runtime_env *renv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address,
+ string_t **str_r)
+{
+ const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+ struct ext_vnd_environment_context *ectx =
+ (struct ext_vnd_environment_context *) this_ext->context;
+ string_t *var_name;
+ const char *ext_value;
+
+ if ( !sieve_binary_read_string(renv->sblock, address, &var_name) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "environment variable operand corrupt: invalid name");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( str_r != NULL ) {
+ const char *vname = str_c(var_name);
+
+ ext_value = ext_environment_item_get_value
+ (ectx->env_ext, renv, vname);
+
+ if ( ext_value == NULL && strchr(vname, '_') != NULL) {
+ char *p, *aname;
+
+ /* Try again with '_' replaced with '-' */
+ aname = t_strdup_noconst(vname);
+ for (p = aname; *p != '\0'; p++) {
+ if (*p == '_')
+ *p = '-';
+ }
+ ext_value = ext_environment_item_get_value
+ (ectx->env_ext, renv, aname);
+ }
+
+ if ( ext_value == NULL ) {
+ *str_r = t_str_new_const("", 0);
+ return SIEVE_EXEC_OK;
+ }
+
+ *str_r = t_str_new_const(ext_value, strlen(ext_value));
+ }
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Namespace registration
+ */
+
+static const struct sieve_extension_objects environment_namespaces =
+ SIEVE_VARIABLES_DEFINE_NAMESPACE(environment_namespace);
+
+const struct sieve_operand_def environment_namespace_operand = {
+ .name = "env-namespace",
+ .ext_def = &vnd_environment_extension,
+ .class = &sieve_variables_namespace_operand_class,
+ .interface = &environment_namespaces
+};
+
+void ext_environment_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+ struct ext_vnd_environment_context *ext_data =
+ (struct ext_vnd_environment_context *) this_ext->context;
+
+ sieve_variables_namespace_register
+ (ext_data->var_ext, valdtr, this_ext, &environment_namespace);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c
new file mode 100644
index 0000000..f8c7028
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/ext-vnd-environment.c
@@ -0,0 +1,112 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension vnd.dovecot.environment
+ * ---------------------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-defined;
+ * spec-bosch-sieve-dovecot-environment
+ * Implementation: preliminary
+ * Status: experimental
+ *
+ */
+
+#include "lib.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-address-parts.h"
+
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "ext-vnd-environment-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_vnd_environment_load
+ (const struct sieve_extension *ext, void **context);
+static void ext_vnd_environment_unload
+ (const struct sieve_extension *ext);
+static bool ext_vnd_environment_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_vnd_environment_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_extension_def vnd_environment_extension = {
+ .name = "vnd.dovecot.environment",
+ .load = ext_vnd_environment_load,
+ .unload = ext_vnd_environment_unload,
+ .validator_load = ext_vnd_environment_validator_load,
+ .interpreter_load = ext_vnd_environment_interpreter_load,
+ SIEVE_EXT_DEFINE_OPERAND(environment_namespace_operand)
+};
+
+static bool ext_vnd_environment_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct ext_vnd_environment_context *ectx;
+
+ if ( *context != NULL )
+ ext_vnd_environment_unload(ext);
+
+ ectx = i_new(struct ext_vnd_environment_context, 1);
+ ectx->env_ext = sieve_ext_environment_require_extension(ext->svinst);
+ ectx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
+ *context = (void *) ectx;
+
+ return TRUE;
+}
+
+static void ext_vnd_environment_unload
+(const struct sieve_extension *ext)
+{
+ struct ext_vnd_environment_context *ectx =
+ (struct ext_vnd_environment_context *) ext->context;
+
+ i_free(ectx);
+}
+
+/*
+ * Validator
+ */
+
+static bool ext_vnd_environment_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ const struct sieve_extension *env_ext;
+
+ /* Load environment extension implicitly */
+
+ env_ext = sieve_validator_extension_load_implicit
+ (valdtr, environment_extension.name);
+ if ( env_ext == NULL )
+ return FALSE;
+
+ ext_environment_variables_init(ext, valdtr);
+ return TRUE;
+}
+
+/*
+ * Interpreter
+ */
+
+static bool ext_vnd_environment_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ ext_vnd_environment_items_register(ext, renv);
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.am b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.am
new file mode 100644
index 0000000..599765f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.am
@@ -0,0 +1,17 @@
+noinst_LTLIBRARIES = libsieve_ext_vnd_report.la
+
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-report.c
+
+libsieve_ext_vnd_report_la_SOURCES = \
+ ext-vnd-report.c \
+ ext-vnd-report-common.c \
+ $(commands)
+
+noinst_HEADERS = \
+ ext-vnd-report-common.h
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in
new file mode 100644
index 0000000..1c2ec8c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in
@@ -0,0 +1,695 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/plugins/vnd.dovecot/report
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_ext_vnd_report_la_LIBADD =
+am__objects_1 = cmd-report.lo
+am_libsieve_ext_vnd_report_la_OBJECTS = ext-vnd-report.lo \
+ ext-vnd-report-common.lo $(am__objects_1)
+libsieve_ext_vnd_report_la_OBJECTS = \
+ $(am_libsieve_ext_vnd_report_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-report.Plo \
+ ./$(DEPDIR)/ext-vnd-report-common.Plo \
+ ./$(DEPDIR)/ext-vnd-report.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_ext_vnd_report_la_SOURCES)
+DIST_SOURCES = $(libsieve_ext_vnd_report_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_ext_vnd_report.la
+AM_CPPFLAGS = \
+ -I$(srcdir)/../../.. \
+ -I$(srcdir)/../../../util \
+ $(LIBDOVECOT_INCLUDE)
+
+commands = \
+ cmd-report.c
+
+libsieve_ext_vnd_report_la_SOURCES = \
+ ext-vnd-report.c \
+ ext-vnd-report-common.c \
+ $(commands)
+
+noinst_HEADERS = \
+ ext-vnd-report-common.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/report/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/plugins/vnd.dovecot/report/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_ext_vnd_report.la: $(libsieve_ext_vnd_report_la_OBJECTS) $(libsieve_ext_vnd_report_la_DEPENDENCIES) $(EXTRA_libsieve_ext_vnd_report_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_ext_vnd_report_la_OBJECTS) $(libsieve_ext_vnd_report_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-report.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vnd-report-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-vnd-report.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-report.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-report-common.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-report.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-report.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-report-common.Plo
+ -rm -f ./$(DEPDIR)/ext-vnd-report.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
new file mode 100644
index 0000000..af53e42
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/cmd-report.c
@@ -0,0 +1,692 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "message-size.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-message.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-vnd-report-common.h"
+
+#include <ctype.h>
+
+/* Report command
+ *
+ * Syntax:
+ * report [:headers_only] <feedback-type: string>
+ * <message: string> <address: string>
+ *
+ */
+
+static bool
+cmd_report_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_report_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool
+cmd_report_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_report = {
+ .identifier = "report",
+ .type = SCT_COMMAND,
+ .positional_args = 3,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_report_registered,
+ .validate = cmd_report_validate,
+ .generate = cmd_report_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+static const struct sieve_argument_def report_headers_only_tag = {
+ .identifier = "headers_only"
+};
+
+/*
+ * Report operation
+ */
+
+static bool
+cmd_report_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_report_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def report_operation = {
+ .mnemonic = "REPORT",
+ .ext_def = &vnd_report_extension,
+ .code = 0,
+ .dump = cmd_report_operation_dump,
+ .execute = cmd_report_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_report_optional {
+ OPT_END,
+ OPT_HEADERS_ONLY
+};
+
+/*
+ * Report action
+ */
+
+/* Forward declarations */
+
+static int
+act_report_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_report_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_report_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+
+/* Action object */
+
+const struct sieve_action_def act_report = {
+ .name = "report",
+ .check_duplicate = act_report_check_duplicate,
+ .print = act_report_print,
+ .commit = act_report_commit
+};
+
+/* Action data */
+
+struct act_report_data {
+ const char *feedback_type;
+ const char *message;
+ struct smtp_address *to_address;
+ bool headers_only:1;
+};
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_report_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &report_headers_only_tag,
+ OPT_HEADERS_ONLY);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_report_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* type */
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg,
+ "feedback-type", 1,
+ SAAT_STRING))
+ return FALSE;
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *fbtype = sieve_ast_argument_str(arg);
+ const char *feedback_type;
+
+ T_BEGIN {
+ /* Check feedback type */
+ feedback_type =
+ ext_vnd_report_parse_feedback_type(str_c(fbtype));
+
+ if (feedback_type == NULL) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "specified feedback type `%s' is invalid",
+ str_sanitize(str_c(fbtype),128));
+ }
+ } T_END;
+
+ if (feedback_type == NULL)
+ return FALSE;
+ }
+ arg = sieve_ast_argument_next(arg);
+
+ /* message */
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "message",
+ 2, SAAT_STRING))
+ return FALSE;
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+ arg = sieve_ast_argument_next(arg);
+
+ /* address */
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "address",
+ 3, SAAT_STRING))
+ return FALSE;
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ /* We can only assess the validity of the outgoing address when it is a
+ string literal. For runtime-generated strings this needs to be done
+ at runtime.
+ */
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *raw_address = sieve_ast_argument_str(arg);
+ const char *error;
+ bool result;
+
+ T_BEGIN {
+ /* Parse the address */
+ result = sieve_address_validate_str(raw_address, &error);
+ if (!result) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "specified report address '%s' is invalid: %s",
+ str_sanitize(str_c(raw_address),128),
+ error);
+ }
+ } T_END;
+
+ return result;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_report_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &report_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_report_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "REPORT");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_HEADERS_ONLY:
+ sieve_code_dumpf(denv, "headers_only");
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ return (sieve_opr_string_dump(denv, address, "feedback-type") &&
+ sieve_opr_string_dump(denv, address, "message") &&
+ sieve_opr_string_dump(denv, address, "address"));
+}
+
+/*
+ * Code execution
+ */
+
+
+static int
+cmd_report_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct act_report_data *act;
+ string_t *fbtype, *message, *to_address;
+ const char *feedback_type, *error;
+ const struct smtp_address *parsed_address;
+ int opt_code = 0, ret = 0;
+ bool headers_only = FALSE;
+ pool_t pool;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_HEADERS_ONLY:
+ headers_only = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Fixed operands */
+
+ if ((ret = sieve_opr_string_read(renv, address, "feedback-type",
+ &fbtype)) <= 0)
+ return ret;
+ if ((ret = sieve_opr_string_read(renv, address, "message",
+ &message)) <= 0)
+ return ret;
+ if ((ret = sieve_opr_string_read(renv, address, "address",
+ &to_address)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ /* Verify and trim feedback type */
+ feedback_type = ext_vnd_report_parse_feedback_type(str_c(fbtype));
+ if (feedback_type == NULL) {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified report feedback type `%s' is invalid",
+ str_sanitize(str_c(fbtype), 256));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Verify and normalize the address to 'local_part@domain' */
+ parsed_address = sieve_address_parse_str(to_address, &error);
+ if (parsed_address == NULL) {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified report address '%s' is invalid: %s",
+ str_sanitize(str_c(to_address),128), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
+ sieve_runtime_trace(renv, 0, "report action");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "report incoming message as `%s' to address %s",
+ str_sanitize(str_c(fbtype), 32),
+ smtp_address_encode_path(parsed_address));
+ }
+
+ /* Add report action to the result */
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_report_data, 1);
+ act->headers_only = headers_only;
+ act->feedback_type = p_strdup(pool, feedback_type);
+ act->message = p_strdup(pool, str_c(message));
+ act->to_address = smtp_address_clone(pool, parsed_address);
+
+ if (sieve_result_add_action(renv, this_ext, "report", &act_report, NULL,
+ (void *)act, 0, TRUE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static bool
+act_report_equals(const struct sieve_script_env *senv ATTR_UNUSED,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2)
+{
+ struct act_report_data *rdd1 = (struct act_report_data *)act1->context;
+ struct act_report_data *rdd2 = (struct act_report_data *)act2->context;
+
+ /* Address is already normalized */
+ return (smtp_address_equals(rdd1->to_address, rdd2->to_address));
+}
+
+static int
+act_report_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return (act_report_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
+}
+
+/* Result printing */
+
+static void
+act_report_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ const struct act_report_data *rdd =
+ (struct act_report_data *)action->context;
+
+ sieve_result_action_printf(rpenv,
+ "report incoming message as `%s' to: %s",
+ str_sanitize(rdd->feedback_type, 32),
+ smtp_address_encode_path(rdd->to_address));
+}
+
+/* Result execution */
+
+static bool _contains_8bit(const char *msg)
+{
+ const unsigned char *s = (const unsigned char *)msg;
+
+ for (; *s != '\0'; s++) {
+ if ((*s & 0x80) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+act_report_send(const struct sieve_action_exec_env *aenv,
+ const struct ext_report_config *config,
+ const struct act_report_data *act)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_message_context *msgctx = aenv->msgctx;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ struct sieve_address_source report_from = config->report_from;
+ const struct smtp_address *sender, *user;
+ struct sieve_smtp_context *sctx;
+ struct istream *input;
+ struct ostream *output;
+ string_t *msg;
+ const char *const *headers;
+ const char *outmsgid, *boundary, *error, *subject, *from;
+ int ret;
+
+ /* Just to be sure */
+ if (!sieve_smtp_available(senv)) {
+ sieve_result_global_warning(
+ aenv, "report action has no means to send mail");
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Make sure we have a subject for our report */
+ if ((ret = mail_get_headers_utf8(msgdata->mail, "subject",
+ &headers)) < 0) {
+ return sieve_result_mail_error(
+ aenv, msgdata->mail,
+ "failed to read header field `subject'");
+ }
+ if (ret > 0 && headers[0] != NULL)
+ subject = t_strconcat("Report: ", headers[0], NULL);
+ else
+ subject = "Report: (message without subject)";
+
+ /* Determine from address */
+ if (report_from.type == SIEVE_ADDRESS_SOURCE_POSTMASTER) {
+ report_from.type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+ report_from.address = NULL;
+ }
+ if (sieve_address_source_get_address(
+ &report_from, svinst, senv, msgctx, eenv->flags,
+ &sender) > 0 && sender != NULL)
+ from = smtp_address_encode_path(sender);
+ else
+ from = sieve_get_postmaster_address(senv);
+
+ /* Start message */
+ sctx = sieve_smtp_start_single(senv, act->to_address, NULL, &output);
+
+ outmsgid = sieve_message_get_new_id(svinst);
+ boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
+
+ /* Compose main report headers */
+ msg = t_str_new(512);
+ rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(msg, "Message-ID", outmsgid);
+ rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+ rfc2822_header_write(msg, "From", from);
+ rfc2822_header_write(msg, "To",
+ smtp_address_encode_path(act->to_address));
+
+ if (_contains_8bit(subject))
+ rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
+ else
+ rfc2822_header_printf(msg, "Subject", "%s", subject);
+
+ rfc2822_header_write(msg, "Auto-Submitted", "auto-generated (report)");
+
+ rfc2822_header_write(msg, "MIME-Version", "1.0");
+ rfc2822_header_printf(msg, "Content-Type",
+ "multipart/report; report-type=feedback-report;\n"
+ "boundary=\"%s\"", boundary);
+
+ str_append(msg, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+
+ /* Human-readable report */
+ str_printfa(msg, "--%s\r\n", boundary);
+ if (_contains_8bit(act->message)) {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=utf-8");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+ } else {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=us-ascii");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+ }
+ rfc2822_header_write(msg, "Content-Disposition", "inline");
+
+ str_printfa(msg, "\r\n%s\r\n\r\n", act->message);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ /* Machine-readable report */
+ str_truncate(msg, 0);
+ str_printfa(msg, "--%s\r\n", boundary);
+ rfc2822_header_write(msg, "Content-Type", "message/feedback-report");
+ str_append(msg, "\r\n");
+
+ rfc2822_header_write(msg, "Version", "1");
+ rfc2822_header_write(msg, "Feedback-Type", act->feedback_type);
+ rfc2822_header_write(msg, "User-Agent",
+ PACKAGE_NAME "/" PACKAGE_VERSION " "
+ PIGEONHOLE_NAME "/" PIGEONHOLE_VERSION);
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
+ const struct smtp_address *sender, *orig_recipient;
+
+ sender = sieve_message_get_sender(msgctx);
+ orig_recipient = sieve_message_get_orig_recipient(msgctx);
+
+ rfc2822_header_write(msg, "Original-Mail-From",
+ smtp_address_encode_path(sender));
+ if (orig_recipient != NULL) {
+ rfc2822_header_write(
+ msg, "Original-Rcpt-To",
+ smtp_address_encode_path(orig_recipient));
+ }
+ }
+ if (svinst->user_email != NULL)
+ user = svinst->user_email;
+ else if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ||
+ (user = sieve_message_get_orig_recipient(msgctx)) == NULL)
+ user = sieve_get_user_email(svinst);
+ if (user != NULL) {
+ rfc2822_header_write(msg, "Dovecot-Reporting-User",
+ smtp_address_encode_path(user));
+ }
+ str_append(msg, "\r\n");
+
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ /* Original message */
+ str_truncate(msg, 0);
+ str_printfa(msg, "--%s\r\n", boundary);
+ if (act->headers_only) {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/rfc822-headers");
+ } else {
+ rfc2822_header_write(msg, "Content-Type", "message/rfc822");
+ }
+ rfc2822_header_write(msg, "Content-Disposition", "attachment");
+ str_append(msg, "\r\n");
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ if (act->headers_only) {
+ struct message_size hdr_size;
+ ret = mail_get_hdr_stream(msgdata->mail, &hdr_size, &input);
+ if (ret >= 0) {
+ input = i_stream_create_limit(
+ input, hdr_size.physical_size);
+ }
+ } else {
+ ret = mail_get_stream(msgdata->mail, NULL, NULL, &input);
+ if (ret >= 0)
+ i_stream_ref(input);
+ }
+ if (ret < 0) {
+ sieve_smtp_abort(sctx);
+ return sieve_result_mail_error(aenv, msgdata->mail,
+ "failed to read input message");
+ }
+
+ o_stream_nsend_istream(output, input);
+
+ if (input->stream_errno != 0) {
+ /* Error; clean up */
+ sieve_result_critical(aenv, "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ i_stream_unref(&input);
+ sieve_smtp_abort(sctx);
+ return SIEVE_EXEC_OK;
+ }
+ i_stream_unref(&input);
+
+ str_truncate(msg, 0);
+ if (!act->headers_only)
+ str_printfa(msg, "\r\n");
+ str_printfa(msg, "\r\n--%s--\r\n", boundary);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ /* Finish sending message */
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(
+ aenv, "failed to send `%s' report to <%s>: %s "
+ "(temporary failure)",
+ str_sanitize(act->feedback_type, 32),
+ smtp_address_encode(act->to_address),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(
+ aenv, "failed to send `%s' report to <%s>: %s "
+ "(permanent failure)",
+ str_sanitize(act->feedback_type, 32),
+ smtp_address_encode(act->to_address),
+ str_sanitize(error, 512));
+ }
+ } else {
+ eenv->exec_status->significant_action_executed = TRUE;
+
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("report_target",
+ smtp_address_encode(act->to_address))->
+ add_str("report_type",
+ str_sanitize(act->feedback_type, 32));
+
+ sieve_result_event_log(aenv, e->event(),
+ "sent `%s' report to <%s>",
+ str_sanitize(act->feedback_type, 32),
+ smtp_address_encode(act->to_address));
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+act_report_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_extension *ext = action->ext;
+ const struct ext_report_config *config =
+ (const struct ext_report_config *)ext->context;
+ const struct act_report_data *act =
+ (const struct act_report_data *)action->context;
+ int ret;
+
+ T_BEGIN {
+ ret = act_report_send(aenv, config, act);
+ } T_END;
+
+ if (ret == SIEVE_EXEC_TEMP_FAILURE)
+ return SIEVE_EXEC_TEMP_FAILURE;
+
+ /* Ignore all other errors */
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
new file mode 100644
index 0000000..d22ad16
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.c
@@ -0,0 +1,51 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "rfc822-parser.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+
+#include "ext-vnd-report-common.h"
+
+bool ext_report_load
+(const struct sieve_extension *ext, void **context)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct ext_report_config *config;
+
+ config = p_new(svinst->pool, struct ext_report_config, 1);
+
+ (void)sieve_address_source_parse_from_setting(svinst,
+ svinst->pool, "sieve_report_from", &config->report_from);
+
+ *context = (void *) config;
+ return TRUE;
+}
+
+const char *
+ext_vnd_report_parse_feedback_type(const char *feedback_type)
+{
+ struct rfc822_parser_context parser;
+ string_t *token;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser,
+ (const unsigned char *)feedback_type, strlen(feedback_type), NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse MIME token */
+ token = t_str_new(64);
+ if (rfc822_parse_mime_token(&parser, token) < 0)
+ return NULL;
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end )
+ return NULL;
+
+ /* Success */
+ return str_c(token);
+}
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
new file mode 100644
index 0000000..11a3757
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report-common.h
@@ -0,0 +1,40 @@
+#ifndef EXT_REPORT_COMMON_H
+#define EXT_REPORT_COMMON_H
+
+/*
+ * Extension configuration
+ */
+
+struct ext_report_config {
+ struct sieve_address_source report_from;
+};
+
+/*
+ * Extension
+ */
+
+extern const struct sieve_extension_def vnd_report_extension;
+
+bool ext_report_load
+ (const struct sieve_extension *ext, void **context);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_report;
+
+/*
+ * Operations
+ */
+
+extern const struct sieve_operation_def report_operation;
+
+/*
+ * RFC 5965 feedback-type
+ */
+
+const char *
+ext_vnd_report_parse_feedback_type(const char *feedback_type);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
new file mode 100644
index 0000000..a5fb64c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/ext-vnd-report.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2016-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension report
+ * ----------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: draft-ietf-sieve-report-00.txt
+ * Implementation: full, but deprecated; provided for backwards compatibility
+ * Status: testing
+ *
+ */
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-actions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "ext-vnd-report-common.h"
+
+/*
+ * Extension
+ */
+
+static bool ext_report_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def vnd_report_extension = {
+ .name = "vnd.dovecot.report",
+ .load = ext_report_load,
+ .validator_load = ext_report_validator_load,
+ SIEVE_EXT_DEFINE_OPERATION(report_operation)
+};
+
+/*
+ * Extension validation
+ */
+
+static bool ext_report_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ /* Register new commands */
+ sieve_validator_register_command(valdtr, ext, &cmd_report);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-actions.c b/pigeonhole/src/lib-sieve/sieve-actions.c
new file mode 100644
index 0000000..86c9954
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-actions.c
@@ -0,0 +1,1096 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "str-sanitize.h"
+#include "unichar.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+#include "ostream.h"
+#include "smtp-params.h"
+#include "mail-storage.h"
+#include "message-date.h"
+#include "message-size.h"
+
+#include "rfc2822.h"
+
+#include "sieve-code.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-actions.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+/*
+ * Action execution environment
+ */
+
+struct event_passthrough *
+sieve_action_create_finish_event(const struct sieve_action_exec_env *aenv)
+{
+ struct event_passthrough *e =
+ event_create_passthrough(aenv->event)->
+ set_name("sieve_action_finished");
+
+ return e;
+}
+
+/*
+ * Action instance
+ */
+
+bool sieve_action_is_executed(const struct sieve_action *act,
+ struct sieve_result *result)
+{
+ unsigned int cur_exec_seq = sieve_result_get_exec_seq(result);
+
+ i_assert(act->exec_seq <= cur_exec_seq);
+ return (act->exec_seq < cur_exec_seq);
+}
+
+/*
+ * Side-effect operand
+ */
+
+const struct sieve_operand_class
+sieve_side_effect_operand_class = { "SIDE-EFFECT" };
+
+bool sieve_opr_side_effect_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ struct sieve_side_effect seffect;
+ const struct sieve_side_effect_def *sdef;
+
+ if (!sieve_opr_object_dump(denv, &sieve_side_effect_operand_class,
+ address, &seffect.object))
+ return FALSE;
+
+ sdef = seffect.def =
+ (const struct sieve_side_effect_def *)seffect.object.def;
+
+ if (sdef->dump_context != NULL) {
+ sieve_code_descend(denv);
+ if (!sdef->dump_context(&seffect, denv, address))
+ return FALSE;
+ sieve_code_ascend(denv);
+ }
+ return TRUE;
+}
+
+int sieve_opr_side_effect_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address,
+ struct sieve_side_effect *seffect)
+{
+ const struct sieve_side_effect_def *sdef;
+ int ret;
+
+ seffect->context = NULL;
+
+ if (!sieve_opr_object_read(renv, &sieve_side_effect_operand_class,
+ address, &seffect->object))
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ sdef = seffect->def =
+ (const struct sieve_side_effect_def *)seffect->object.def;
+
+ if (sdef->read_context != NULL &&
+ (ret = sdef->read_context(seffect, renv, address,
+ &seffect->context)) <= 0)
+ return ret;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_action_opr_optional_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address, signed int *opt_code)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if (opt_code == NULL) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while (opok) {
+ int opt;
+
+ opt = sieve_opr_optional_dump(denv, address, opt_code);
+ if (opt <= 0)
+ return opt;
+
+ if (*opt_code == SIEVE_OPT_SIDE_EFFECT)
+ opok = sieve_opr_side_effect_dump(denv, address);
+ else
+ return (final ? -1 : 1);
+ }
+
+ return -1;
+}
+
+int sieve_action_opr_optional_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address,
+ signed int *opt_code, int *exec_status,
+ struct sieve_side_effects_list **list)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE;
+ int ret;
+
+ if (opt_code == NULL) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if (exec_status != NULL)
+ *exec_status = SIEVE_EXEC_OK;
+
+ for (;;) {
+ int opt;
+
+ opt = sieve_opr_optional_read(renv, address, opt_code);
+ if (opt <= 0) {
+ if (opt < 0 && exec_status != NULL)
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return opt;
+ }
+
+ if (*opt_code == SIEVE_OPT_SIDE_EFFECT) {
+ struct sieve_side_effect seffect;
+
+ i_assert(list != NULL);
+
+ ret = sieve_opr_side_effect_read(renv, address,
+ &seffect);
+ if (ret <= 0) {
+ if (exec_status != NULL)
+ *exec_status = ret;
+ return -1;
+ }
+
+ if (*list == NULL) {
+ *list = sieve_side_effects_list_create(
+ renv->result);
+ }
+
+ sieve_side_effects_list_add(*list, &seffect);
+ } else {
+ if (final) {
+ sieve_runtime_trace_error(
+ renv, "invalid optional operand");
+ if (exec_status != NULL)
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+/*
+ * Store action
+ */
+
+/* Forward declarations */
+
+static bool
+act_store_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2);
+
+static int
+act_store_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_store_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context);
+static int
+act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep);
+static int
+act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
+static void
+act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool success);
+
+/* Action object */
+
+const struct sieve_action_def act_store = {
+ .name = "store",
+ .flags =
+ SIEVE_ACTFLAG_TRIES_DELIVER |
+ SIEVE_ACTFLAG_MAIL_STORAGE,
+ .equals = act_store_equals,
+ .check_duplicate = act_store_check_duplicate,
+ .print = act_store_print,
+ .start = act_store_start,
+ .execute = act_store_execute,
+ .commit = act_store_commit,
+ .rollback = act_store_rollback,
+};
+
+/* API */
+
+int sieve_act_store_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const char *mailbox)
+{
+ pool_t pool;
+ struct act_store_context *act;
+
+ /* Add redirect action to the result */
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_store_context, 1);
+ act->mailbox = p_strdup(pool, mailbox);
+
+ return sieve_result_add_action(renv, NULL, name, &act_store, seffects,
+ (void *)act, 0, TRUE);
+}
+
+void sieve_act_store_add_flags(const struct sieve_action_exec_env *aenv,
+ void *tr_context, const char *const *keywords,
+ enum mail_flags flags)
+{
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+
+ i_assert(trans != NULL);
+
+ /* Assign mail keywords for subsequent mailbox_copy() */
+ if (*keywords != NULL) {
+ const char *const *kw;
+
+ if (!array_is_created(&trans->keywords)) {
+ pool_t pool = sieve_result_pool(aenv->result);
+ p_array_init(&trans->keywords, pool, 2);
+ }
+
+ kw = keywords;
+ while (*kw != NULL) {
+ array_append(&trans->keywords, kw, 1);
+ kw++;
+ }
+ }
+
+ /* Assign mail flags for subsequent mailbox_copy() */
+ trans->flags |= flags;
+
+ trans->flags_altered = TRUE;
+}
+
+/* Equality */
+
+static bool
+act_store_equals(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2)
+{
+ struct act_store_context *st_ctx1 =
+ (act1 == NULL ?
+ NULL : (struct act_store_context *)act1->context);
+ struct act_store_context *st_ctx2 =
+ (act2 == NULL ?
+ NULL : (struct act_store_context *)act2->context);
+ const char *mailbox1, *mailbox2;
+
+ /* FIXME: consider namespace aliases */
+
+ if (st_ctx1 == NULL && st_ctx2 == NULL)
+ return TRUE;
+
+ mailbox1 = (st_ctx1 == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx1->mailbox);
+ mailbox2 = (st_ctx2 == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx2->mailbox);
+
+ if (strcmp(mailbox1, mailbox2) == 0)
+ return TRUE;
+
+ return (strcasecmp(mailbox1, "INBOX") == 0 &&
+ strcasecmp(mailbox2, "INBOX") == 0);
+}
+
+/* Result verification */
+
+static int
+act_store_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+
+ return (act_store_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
+}
+
+/* Result printing */
+
+static void
+act_store_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep)
+{
+ struct act_store_context *ctx =
+ (struct act_store_context *)action->context;
+ const char *mailbox;
+
+ mailbox = (ctx == NULL ?
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(rpenv->scriptenv) :
+ ctx->mailbox);
+
+ sieve_result_action_printf(rpenv, "store message in folder: %s",
+ str_sanitize(mailbox, 128));
+
+ *keep = FALSE;
+}
+
+/* Action implementation */
+
+void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans)
+{
+ pool_t pool = sieve_result_pool(aenv->result);
+
+ trans->error = p_strdup(pool,
+ mailbox_get_last_internal_error(trans->box,
+ &trans->error_code));
+}
+
+static bool
+act_store_mailbox_alloc(const struct sieve_action_exec_env *aenv,
+ const char *mailbox, struct mailbox **box_r,
+ enum mail_error *error_code_r, const char **error_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct mailbox *box;
+ struct mail_storage **storage = &(eenv->exec_status->last_storage);
+ enum mailbox_flags flags = MAILBOX_FLAG_POST_SESSION;
+
+ *box_r = NULL;
+ *error_code_r = MAIL_ERROR_NONE;
+ *error_r = NULL;
+
+ if (!uni_utf8_str_is_valid(mailbox)) {
+ /* Just a precaution; already (supposed to be) checked at
+ compiletime/runtime.
+ */
+ *error_r = t_strdup_printf("mailbox name not utf-8: %s",
+ mailbox);
+ *error_code_r = MAIL_ERROR_PARAMS;
+ return FALSE;
+ }
+
+ if (eenv->scriptenv->mailbox_autocreate)
+ flags |= MAILBOX_FLAG_AUTO_CREATE;
+ if (eenv->scriptenv->mailbox_autosubscribe)
+ flags |= MAILBOX_FLAG_AUTO_SUBSCRIBE;
+ *box_r = box = mailbox_alloc_for_user(eenv->scriptenv->user, mailbox,
+ flags);
+ *storage = mailbox_get_storage(box);
+
+ return TRUE;
+}
+
+static int
+act_store_start(const struct sieve_action_exec_env *aenv, void **tr_context)
+{
+ const struct sieve_action *action = aenv->action;
+ struct act_store_context *ctx =
+ (struct act_store_context *)action->context;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct act_store_transaction *trans;
+ struct mailbox *box = NULL;
+ pool_t pool = sieve_result_pool(aenv->result);
+ const char *error = NULL;
+ enum mail_error error_code = MAIL_ERROR_NONE;
+ bool disabled = FALSE, alloc_failed = FALSE;
+
+ /* If context is NULL, the store action is the result of (implicit)
+ keep.
+ */
+ if (ctx == NULL) {
+ ctx = p_new(pool, struct act_store_context, 1);
+ ctx->mailbox =
+ p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv));
+ }
+
+ e_debug(aenv->event, "Start storing into mailbox %s", ctx->mailbox);
+
+ /* Open the requested mailbox */
+
+ /* NOTE: The caller of the sieve library is allowed to leave user set
+ to NULL. This implementation will then skip actually storing the
+ message.
+ */
+ if (senv->user != NULL) {
+ if (!act_store_mailbox_alloc(aenv, ctx->mailbox, &box,
+ &error_code, &error))
+ alloc_failed = TRUE;
+ } else {
+ disabled = TRUE;
+ }
+
+ /* Create transaction context */
+ trans = p_new(pool, struct act_store_transaction, 1);
+
+ trans->context = ctx;
+ trans->box = box;
+ trans->flags = 0;
+
+ trans->mailbox_name = ctx->mailbox;
+ trans->mailbox_identifier =
+ p_strdup_printf(pool, "'%s'", str_sanitize(ctx->mailbox, 256));
+
+ trans->disabled = disabled;
+
+ if (alloc_failed) {
+ trans->error = p_strdup(pool, error);
+ trans->error_code = error_code;
+ e_debug(aenv->event, "Failed to open mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+ } else {
+ trans->error_code = MAIL_ERROR_NONE;
+ }
+
+ *tr_context = (void *)trans;
+
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ case MAIL_ERROR_NOTFOUND:
+ return SIEVE_EXEC_OK;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ break;
+ }
+ return SIEVE_EXEC_FAILURE;
+}
+
+static struct mail_keywords *
+act_store_keywords_create(const struct sieve_action_exec_env *aenv,
+ ARRAY_TYPE(const_string) *keywords,
+ struct mailbox *box, bool create_empty)
+{
+ struct mail_keywords *box_keywords = NULL;
+ const char *const *kwds = NULL;
+
+ if (!array_is_created(keywords) || array_count(keywords) == 0) {
+ if (!create_empty)
+ return NULL;
+ } else {
+ ARRAY_TYPE(const_string) valid_keywords;
+ const char *error;
+ unsigned int count, i;
+
+ kwds = array_get(keywords, &count);
+ t_array_init(&valid_keywords, count);
+
+ for (i = 0; i < count; i++) {
+ if (mailbox_keyword_is_valid(box, kwds[i], &error)) {
+ array_append(&valid_keywords, &kwds[i], 1);
+ continue;
+ }
+
+ sieve_result_warning(aenv,
+ "specified IMAP keyword '%s' is invalid "
+ "(ignored): %s", str_sanitize(kwds[i], 64),
+ sieve_error_from_external(error));
+ }
+
+ array_append_zero(keywords);
+ kwds = array_idx(keywords, 0);
+ }
+
+ if (mailbox_keywords_create(box, kwds, &box_keywords) < 0) {
+ sieve_result_error(
+ aenv, "invalid keywords set for stored message");
+ return NULL;
+ }
+
+ return box_keywords;
+}
+
+static bool have_equal_keywords(struct mail *mail, struct mail_keywords *new_kw)
+{
+ const ARRAY_TYPE(keyword_indexes) *old_kw_arr =
+ mail_get_keyword_indexes(mail);
+ const unsigned int *old_kw;
+ unsigned int i, j;
+
+ if (array_count(old_kw_arr) != new_kw->count)
+ return FALSE;
+ if (new_kw->count == 0)
+ return TRUE;
+
+ old_kw = array_front(old_kw_arr);
+ for (i = 0; i < new_kw->count; i++) {
+ /* new_kw->count equals old_kw's count and it's easier to use */
+ for (j = 0; j < new_kw->count; j++) {
+ if (old_kw[j] == new_kw->idx[i])
+ break;
+ }
+ if (j == new_kw->count)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int
+act_store_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool *keep)
+{
+ const struct sieve_action *action = aenv->action;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+ struct mail *mail = (action->mail != NULL ?
+ action->mail : eenv->msgdata->mail);
+ struct mail_save_context *save_ctx;
+ struct mail_keywords *keywords = NULL;
+ struct mailbox *box;
+ bool backends_equal = FALSE;
+ int status = SIEVE_EXEC_OK;
+
+ /* Verify transaction */
+ if (trans == NULL)
+ return SIEVE_EXEC_FAILURE;
+ box = trans->box;
+
+ /* Check whether we need to do anything */
+ if (trans->disabled) {
+ e_debug(aenv->event, "Skip storing into mailbox %s",
+ trans->mailbox_identifier);
+ *keep = FALSE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Exit early if mailbox is not available */
+ if (box == NULL)
+ return SIEVE_EXEC_FAILURE;
+
+ e_debug(aenv->event, "Execute storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ /* Mark attempt to use storage. Can only get here when all previous
+ actions succeeded.
+ */
+ eenv->exec_status->last_storage = mailbox_get_storage(box);
+
+ /* Open the mailbox (may already be open) */
+ if (trans->error_code == MAIL_ERROR_NONE) {
+ if (mailbox_open(box) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+ e_debug(aenv->event, "Failed to open mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+ }
+ }
+
+ /* Exit early if transaction already failed */
+ switch (trans->error_code) {
+ case MAIL_ERROR_NONE:
+ break;
+ case MAIL_ERROR_TEMP:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ default:
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* If the message originates from the target mailbox, only update the
+ flags and keywords (if not read-only)
+ */
+ if (mailbox_backends_equal(box, mail->box)) {
+ backends_equal = TRUE;
+ } else {
+ struct mail *real_mail;
+
+ if (mail_get_backend_mail(mail, &real_mail) < 0)
+ return SIEVE_EXEC_FAILURE;
+ if (real_mail != mail &&
+ mailbox_backends_equal(box, real_mail->box))
+ backends_equal = TRUE;
+ }
+ if (backends_equal) {
+ trans->redundant = TRUE;
+
+ if (trans->flags_altered && !mailbox_is_readonly(mail->box)) {
+ keywords = act_store_keywords_create(
+ aenv, &trans->keywords, mail->box, TRUE);
+
+ if (keywords != NULL) {
+ if (!have_equal_keywords(mail, keywords)) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mail_update_keywords(mail, MODIFY_REPLACE, keywords);
+ }
+ mailbox_keywords_unref(&keywords);
+ }
+
+ if ((mail_get_flags(mail) & MAIL_FLAGS_NONRECENT) != trans->flags) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mail_update_flags(mail, MODIFY_REPLACE, trans->flags);
+ }
+ }
+ e_debug(aenv->event, "Updated existing mail in mailbox %s",
+ trans->mailbox_identifier);
+ return SIEVE_EXEC_OK;
+
+ /* If the message is modified, only store it in the source mailbox when
+ it is not opened read-only. Mail structs of modified messages have
+ their own mailbox, unrelated to the orignal mail, so this case needs
+ to be handled separately.
+ */
+ } else if (mail != eenv->msgdata->mail &&
+ mailbox_is_readonly(eenv->msgdata->mail->box) &&
+ (mailbox_backends_equal(box, eenv->msgdata->mail->box))) {
+ e_debug(aenv->event,
+ "Not modifying exsiting mail in read-only mailbox %s",
+ trans->mailbox_identifier);
+ trans->redundant = TRUE;
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Mark attempt to store in default mailbox */
+ if (strcmp(trans->context->mailbox,
+ SIEVE_SCRIPT_DEFAULT_MAILBOX(eenv->scriptenv)) == 0)
+ eenv->exec_status->tried_default_save = TRUE;
+
+ /* Start mail transaction */
+ trans->mail_trans = mailbox_transaction_begin(
+ box, MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
+
+ /* Store the message */
+ save_ctx = mailbox_save_alloc(trans->mail_trans);
+
+ /* Apply keywords and flags that side-effects may have added */
+ if (trans->flags_altered) {
+ keywords = act_store_keywords_create(aenv, &trans->keywords,
+ box, FALSE);
+
+ if (trans->flags != 0 || keywords != NULL) {
+ eenv->exec_status->significant_action_executed = TRUE;
+ mailbox_save_set_flags(save_ctx, trans->flags, keywords);
+ }
+ } else {
+ mailbox_save_copy_flags(save_ctx, mail);
+ }
+
+ if (mailbox_save_using_mail(&save_ctx, mail) < 0) {
+ sieve_act_store_get_storage_error(aenv, trans);
+ e_debug(aenv->event, "Failed to save to mailbox %s: %s",
+ trans->mailbox_identifier, trans->error);
+
+ status = (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+ } else {
+ e_debug(aenv->event, "Saving to mailbox %s successful so far",
+ trans->mailbox_identifier);
+ eenv->exec_status->significant_action_executed = TRUE;
+ }
+
+ /* Deallocate keywords */
+ if (keywords != NULL)
+ mailbox_keywords_unref(&keywords);
+
+ /* Cancel implicit keep if all went well so far */
+ *keep = (status < SIEVE_EXEC_OK);
+
+ return status;
+}
+
+static void
+act_store_log_status(struct act_store_transaction *trans,
+ const struct sieve_action_exec_env *aenv,
+ bool rolled_back, bool status)
+{
+ const char *mailbox_name = trans->mailbox_name;
+ const char *mailbox_identifier = trans->mailbox_identifier;
+
+ if (trans->box != NULL) {
+ const char *mailbox_vname = str_sanitize(mailbox_get_vname(trans->box), 128);
+
+ if (strcmp(trans->mailbox_name, mailbox_vname) != 0) {
+ mailbox_identifier = t_strdup_printf(
+ "%s (%s)", mailbox_identifier,
+ str_sanitize(mailbox_vname, 256));
+ }
+ }
+
+ /* Store disabled? */
+ if (trans->disabled) {
+ sieve_result_global_log(aenv, "store into mailbox %s skipped",
+ mailbox_identifier);
+ /* Store redundant? */
+ } else if (trans->redundant) {
+ sieve_result_global_log(aenv, "left message in mailbox %s",
+ mailbox_identifier);
+ /* Store failed? */
+ } else if (!status) {
+ const char *errstr;
+ enum mail_error error_code;
+
+ if (trans->error == NULL)
+ sieve_act_store_get_storage_error(aenv, trans);
+
+ errstr = trans->error;
+ error_code = trans->error_code;
+
+ if (error_code == MAIL_ERROR_NOQUOTA) {
+ /* Never log quota problems as error in global log */
+ sieve_result_global_log_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ } else if (error_code == MAIL_ERROR_NOTFOUND ||
+ error_code == MAIL_ERROR_PARAMS ||
+ error_code == MAIL_ERROR_PERM) {
+ sieve_result_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ } else {
+ sieve_result_global_error(
+ aenv, "failed to store into mailbox %s: %s",
+ mailbox_identifier, errstr);
+ }
+ /* Store aborted? */
+ } else if (rolled_back) {
+ if (!aenv->action->keep) {
+ sieve_result_global_log(
+ aenv, "store into mailbox %s aborted",
+ mailbox_identifier);
+ } else {
+ e_debug(aenv->event, "Store into mailbox %s aborted",
+ mailbox_identifier);
+ }
+ /* Succeeded */
+ } else {
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("fileinto_mailbox_name", mailbox_name)->
+ add_str("fileinto_mailbox", mailbox_identifier);
+ sieve_result_event_log(aenv, e->event(),
+ "stored mail into mailbox %s",
+ mailbox_identifier);
+ }
+}
+
+static void act_store_cleanup(struct act_store_transaction *trans)
+{
+ if (trans->mail_trans != NULL)
+ mailbox_transaction_rollback(&trans->mail_trans);
+ if (trans->box != NULL)
+ mailbox_free(&trans->box);
+}
+
+static int
+act_store_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+ bool bail_out = FALSE, status = TRUE;
+ int ret = SIEVE_EXEC_OK;
+
+ /* Verify transaction */
+ if (trans == NULL)
+ return SIEVE_EXEC_FAILURE;
+
+ e_debug(aenv->event, "Commit storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ /* Check whether we can commit this transaction */
+ if (trans->error_code != MAIL_ERROR_NONE) {
+ /* Transaction already failed */
+ bail_out = TRUE;
+ status = FALSE;
+ if (trans->error_code == MAIL_ERROR_TEMP)
+ ret = SIEVE_EXEC_TEMP_FAILURE;
+ else
+ ret = SIEVE_EXEC_FAILURE;
+ /* Check whether we need to do anything */
+ } else if (trans->disabled) {
+ /* Nothing to do */
+ bail_out = TRUE;
+ } else if (trans->redundant) {
+ /* This transaction is redundant */
+ bail_out = TRUE;
+ eenv->exec_status->keep_original = TRUE;
+ eenv->exec_status->message_saved = TRUE;
+ }
+ if (bail_out) {
+ act_store_log_status(trans, aenv, FALSE, status);
+ act_store_cleanup(trans);
+ return ret;
+ }
+
+ i_assert(trans->box != NULL);
+ i_assert(trans->mail_trans != NULL);
+
+ /* Mark attempt to use storage. Can only get here when all previous
+ actions succeeded.
+ */
+ eenv->exec_status->last_storage = mailbox_get_storage(trans->box);
+
+ /* Commit mailbox transaction */
+ status = (mailbox_transaction_commit(&trans->mail_trans) == 0);
+
+ /* Note the fact that the message was stored at least once */
+ if (status)
+ eenv->exec_status->message_saved = TRUE;
+ else
+ eenv->exec_status->store_failed = TRUE;
+
+ /* Log our status */
+ act_store_log_status(trans, aenv, FALSE, status);
+
+ /* Clean up */
+ act_store_cleanup(trans);
+
+ if (status)
+ return SIEVE_EXEC_OK;
+
+ return (trans->error_code == MAIL_ERROR_TEMP ?
+ SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
+}
+
+static void
+act_store_rollback(const struct sieve_action_exec_env *aenv, void *tr_context,
+ bool success)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct act_store_transaction *trans =
+ (struct act_store_transaction *)tr_context;
+
+ if (trans == NULL)
+ return;
+
+ e_debug(aenv->event, "Roll back storing into mailbox %s",
+ trans->mailbox_identifier);
+
+ i_assert(trans->box != NULL);
+
+ if (!success) {
+ eenv->exec_status->last_storage =
+ mailbox_get_storage(trans->box);
+ eenv->exec_status->store_failed = TRUE;
+ }
+
+ /* Log status */
+ act_store_log_status(trans, aenv, TRUE, success);
+
+ /* Rollback mailbox transaction and clean up */
+ act_store_cleanup(trans);
+}
+
+/*
+ * Redirect action
+ */
+
+int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const struct smtp_address *to_address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ struct act_redirect_context *act;
+ pool_t pool;
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct act_redirect_context, 1);
+ act->to_address = smtp_address_clone(pool, to_address);
+
+ if (sieve_result_add_action(renv, NULL, name, &act_redirect, seffects,
+ (void *)act, svinst->max_redirects,
+ TRUE) < 0)
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action utility functions
+ */
+
+/* Rejecting the mail */
+
+static int
+sieve_action_do_reject_mail(const struct sieve_action_exec_env *aenv,
+ const struct smtp_address *recipient,
+ const char *reason)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_instance *svinst = eenv->svinst;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ const struct smtp_address *sender, *orig_recipient;
+ struct istream *input;
+ struct ostream *output;
+ struct sieve_smtp_context *sctx;
+ const char *new_msgid, *boundary, *error;
+ string_t *hdr;
+ int ret;
+
+ sender = sieve_message_get_sender(aenv->msgctx);
+ orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
+
+ sctx = sieve_smtp_start_single(senv, sender, NULL, &output);
+
+ /* Just to be sure */
+ if (sctx == NULL) {
+ sieve_result_global_warning(
+ aenv, "reject action has no means to send mail");
+ return SIEVE_EXEC_OK;
+ }
+
+ new_msgid = sieve_message_get_new_id(svinst);
+ boundary = t_strdup_printf("%s/%s", my_pid, svinst->hostname);
+
+ hdr = t_str_new(512);
+ rfc2822_header_write(hdr, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(hdr, "Message-ID", new_msgid);
+ rfc2822_header_write(hdr, "Date", message_date_create(ioloop_time));
+ rfc2822_header_write(hdr, "From", sieve_get_postmaster_address(senv));
+ rfc2822_header_printf(hdr, "To", "<%s>", smtp_address_encode(sender));
+ rfc2822_header_write(hdr, "Subject", "Automatically rejected mail");
+ rfc2822_header_write(hdr, "Auto-Submitted", "auto-replied (rejected)");
+ rfc2822_header_write(hdr, "Precedence", "bulk");
+
+ rfc2822_header_write(hdr, "MIME-Version", "1.0");
+ rfc2822_header_printf(hdr, "Content-Type",
+ "multipart/report; "
+ "report-type=disposition-notification;\r\n"
+ "boundary=\"%s\"", boundary);
+
+ str_append(hdr, "\r\nThis is a MIME-encapsulated message\r\n\r\n");
+
+ /* Human readable status report */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type", "text/plain; charset=utf-8");
+ rfc2822_header_write(hdr, "Content-Disposition", "inline");
+ rfc2822_header_write(hdr, "Content-Transfer-Encoding", "8bit");
+
+ str_printfa(hdr, "\r\nYour message to <%s> was automatically rejected:\r\n"
+ "%s\r\n", smtp_address_encode(recipient), reason);
+
+ /* MDN status report */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type",
+ "message/disposition-notification");
+ str_append(hdr, "\r\n");
+ rfc2822_header_write(hdr,
+ "Reporting-UA: %s; Dovecot Mail Delivery Agent",
+ svinst->hostname);
+ if (orig_recipient != NULL) {
+ rfc2822_header_printf(hdr, "Original-Recipient", "rfc822; %s",
+ smtp_address_encode(orig_recipient));
+ }
+ rfc2822_header_printf(hdr, "Final-Recipient", "rfc822; %s",
+ smtp_address_encode(recipient));
+
+ if (msgdata->id != NULL)
+ rfc2822_header_write(hdr, "Original-Message-ID", msgdata->id);
+ rfc2822_header_write(hdr, "Disposition",
+ "automatic-action/MDN-sent-automatically; deleted");
+ str_append(hdr, "\r\n");
+
+ /* original message's headers */
+ str_printfa(hdr, "--%s\r\n", boundary);
+ rfc2822_header_write(hdr, "Content-Type", "message/rfc822");
+ str_append(hdr, "\r\n");
+ o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+ if (mail_get_hdr_stream(msgdata->mail, NULL, &input) == 0) {
+ /* NOTE: If you add more headers, they need to be sorted. We'll
+ drop Content-Type because we're not including the message
+ body, and having a multipart Content-Type may confuse some
+ MIME parsers when they don't see the message boundaries.
+ */
+ static const char *const exclude_headers[] = {
+ "Content-Type"
+ };
+
+ input = i_stream_create_header_filter(
+ input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR |
+ HEADER_FILTER_HIDE_BODY,
+ exclude_headers, N_ELEMENTS(exclude_headers),
+ *null_header_filter_callback, (void *)NULL);
+ o_stream_nsend_istream(output, input);
+ i_stream_unref(&input);
+ }
+
+ str_truncate(hdr, 0);
+ str_printfa(hdr, "\r\n\r\n--%s--\r\n", boundary);
+ o_stream_nsend(output, str_data(hdr), str_len(hdr));
+
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(aenv,
+ "failed to send rejection message to <%s>: %s "
+ "(temporary failure)",
+ smtp_address_encode(sender),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(aenv,
+ "failed to send rejection message to <%s>: %s "
+ "(permanent failure)",
+ smtp_address_encode(sender),
+ str_sanitize(error, 512));
+ }
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_action_reject_mail(const struct sieve_action_exec_env *aenv,
+ const struct smtp_address *recipient,
+ const char *reason)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ int result;
+
+ T_BEGIN {
+ if (senv->reject_mail != NULL) {
+ result = (senv->reject_mail(senv, recipient,
+ reason) >= 0 ?
+ SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
+ } else {
+ result = sieve_action_do_reject_mail(aenv, recipient,
+ reason);
+ }
+ } T_END;
+
+ return result;
+}
+
+/*
+ * Mailbox
+ */
+
+bool sieve_mailbox_check_name(const char *mailbox, const char **error_r)
+{
+ if (!uni_utf8_str_is_valid(mailbox)) {
+ *error_r = "invalid utf-8";
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/sieve-actions.h b/pigeonhole/src/lib-sieve/sieve-actions.h
new file mode 100644
index 0000000..f1a447f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-actions.h
@@ -0,0 +1,311 @@
+#ifndef SIEVE_ACTIONS_H
+#define SIEVE_ACTIONS_H
+
+#include "lib.h"
+#include "mail-types.h"
+#include "mail-error.h"
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+#include "sieve-extensions.h"
+#include "sieve-execute.h"
+
+/*
+ * Action execution environment
+ */
+
+struct sieve_action_exec_env {
+ const struct sieve_execute_env *exec_env;
+ struct sieve_result_execution *rexec;
+ const struct sieve_action *action;
+ struct event *event;
+
+ struct sieve_result *result;
+ struct sieve_error_handler *ehandler;
+
+ struct sieve_message_context *msgctx;
+};
+
+struct event_passthrough *
+sieve_action_create_finish_event(const struct sieve_action_exec_env *aenv);
+
+/*
+ * Action flags
+ */
+
+enum sieve_action_flags {
+ SIEVE_ACTFLAG_TRIES_DELIVER = (1 << 0),
+ SIEVE_ACTFLAG_SENDS_RESPONSE = (1 << 1),
+ SIEVE_ACTFLAG_MAIL_STORAGE = (1 << 2)
+};
+
+/*
+ * Action definition
+ */
+
+struct sieve_action_def {
+ const char *name;
+ unsigned int flags;
+
+ bool (*equals)(const struct sieve_script_env *senv,
+ const struct sieve_action *act1,
+ const struct sieve_action *act2);
+
+ /* Result verification */
+ int (*check_duplicate)(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+ int (*check_conflict)(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+
+ /* Result printing */
+ void (*print)(const struct sieve_action *action,
+ const struct sieve_result_print_env *penv, bool *keep);
+
+ /* Result execution */
+ int (*start)(const struct sieve_action_exec_env *aenv,
+ void **tr_context);
+ int (*execute)(const struct sieve_action_exec_env *aenv,
+ void *tr_context, bool *keep);
+ int (*commit)(const struct sieve_action_exec_env *aenv,
+ void *tr_context);
+ void (*rollback)(const struct sieve_action_exec_env *aenv,
+ void *tr_context, bool success);
+ void (*finish)(const struct sieve_action_exec_env *aenv,
+ void *tr_context, int status);
+};
+
+/*
+ * Action instance
+ */
+
+struct sieve_action {
+ const struct sieve_action_def *def;
+ const struct sieve_extension *ext;
+ struct event *event;
+
+ const char *name;
+ const char *location;
+ unsigned int exec_seq;
+ void *context;
+ struct mail *mail;
+
+ bool keep:1;
+};
+
+#define sieve_action_is(act, definition) ((act)->def == &(definition))
+#define sieve_action_name(act) ((act)->name)
+
+bool sieve_action_is_executed(const struct sieve_action *act,
+ struct sieve_result *result);
+
+/*
+ * Action side effects
+ */
+
+/* Side effect object */
+
+struct sieve_side_effect_def {
+ struct sieve_object_def obj_def;
+
+ /* Precedence (side effects with higher value are executed first) */
+
+ unsigned int precedence;
+
+ /* The action it is supposed to link to */
+ const struct sieve_action_def *to_action;
+
+ /* Context coding */
+ bool (*dump_context)(const struct sieve_side_effect *seffect,
+ const struct sieve_dumptime_env *renv,
+ sieve_size_t *address);
+ int (*read_context)(const struct sieve_side_effect *seffect,
+ const struct sieve_runtime_env *renv,
+ sieve_size_t *address, void **se_context);
+
+ /* Result verification */
+ int (*merge)(const struct sieve_runtime_env *renv,
+ const struct sieve_action *action,
+ const struct sieve_side_effect *old_seffect,
+ const struct sieve_side_effect *new_seffect,
+ void **old_context);
+
+ /* Result printing */
+ void (*print)(const struct sieve_side_effect *seffect,
+ const struct sieve_action *action,
+ const struct sieve_result_print_env *penv, bool *keep);
+
+ /* Result execution */
+
+ int (*pre_execute)(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void **se_tr_context);
+ int (*post_execute)(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void *se_tr_context, bool *keep);
+ void (*post_commit)(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void *se_tr_context,
+ int commit_status);
+ void (*rollback)(const struct sieve_side_effect *seffect,
+ const struct sieve_action_exec_env *aenv,
+ void *tr_context, void *se_tr_context, bool success);
+};
+
+struct sieve_side_effect {
+ struct sieve_object object;
+
+ const struct sieve_side_effect_def *def;
+
+ void *context;
+};
+
+/*
+ * Side effect operand
+ */
+
+#define SIEVE_EXT_DEFINE_SIDE_EFFECT(SEF) SIEVE_EXT_DEFINE_OBJECT(SEF)
+#define SIEVE_EXT_DEFINE_SIDE_EFFECTS(SEFS) SIEVE_EXT_DEFINE_OBJECTS(SEFS)
+
+#define SIEVE_OPT_SIDE_EFFECT (-1)
+
+extern const struct sieve_operand_class sieve_side_effect_operand_class;
+
+static inline void
+sieve_opr_side_effect_emit(struct sieve_binary_block *sblock,
+ const struct sieve_extension *ext,
+ const struct sieve_side_effect_def *seff)
+{
+ sieve_opr_object_emit(sblock, ext, &seff->obj_def);
+}
+
+bool sieve_opr_side_effect_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+int sieve_opr_side_effect_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address,
+ struct sieve_side_effect *seffect);
+
+/*
+ * Optional operands
+ */
+
+int sieve_action_opr_optional_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address, signed int *opt_code);
+
+int sieve_action_opr_optional_read(const struct sieve_runtime_env *renv,
+ sieve_size_t *address, signed int *opt_code,
+ int *exec_status,
+ struct sieve_side_effects_list **list);
+
+/*
+ * Core actions
+ */
+
+extern const struct sieve_action_def act_redirect;
+extern const struct sieve_action_def act_store;
+extern const struct sieve_action_def act_discard;
+
+/*
+ * Store action
+ */
+
+struct act_store_context {
+ /* Folder name represented in utf-8 */
+ const char *mailbox;
+};
+
+struct act_store_transaction {
+ struct act_store_context *context;
+ struct mailbox *box;
+ struct mailbox_transaction_context *mail_trans;
+
+ const char *mailbox_name;
+ const char *mailbox_identifier;
+
+ const char *error;
+ enum mail_error error_code;
+
+ enum mail_flags flags;
+ ARRAY_TYPE(const_string) keywords;
+
+ bool flags_altered:1;
+ bool disabled:1;
+ bool redundant:1;
+};
+
+int sieve_act_store_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const char *folder);
+
+void sieve_act_store_add_flags(const struct sieve_action_exec_env *aenv,
+ void *tr_context, const char *const *keywords,
+ enum mail_flags flags);
+
+void sieve_act_store_get_storage_error(const struct sieve_action_exec_env *aenv,
+ struct act_store_transaction *trans);
+
+/*
+ * Redirect action
+ */
+
+struct act_redirect_context {
+ const struct smtp_address *to_address;
+};
+
+int sieve_act_redirect_add_to_result(const struct sieve_runtime_env *renv,
+ const char *name,
+ struct sieve_side_effects_list *seffects,
+ const struct smtp_address *to_address);
+
+/*
+ * Checking for duplicates
+ */
+
+static inline bool
+sieve_action_duplicate_check_available(const struct sieve_action_exec_env *aenv)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+
+ return sieve_execute_duplicate_check_available(eenv);
+}
+
+static inline int
+sieve_action_duplicate_check(const struct sieve_action_exec_env *aenv,
+ const void *id, size_t id_size,
+ bool *duplicate_r)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+
+ return sieve_execute_duplicate_check(eenv, id, id_size,
+ duplicate_r);
+}
+
+static inline void
+sieve_action_duplicate_mark(const struct sieve_action_exec_env *aenv,
+ const void *id, size_t id_size, time_t time)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+
+ return sieve_execute_duplicate_mark(eenv, id, id_size, time);
+}
+
+/*
+ * Action utility functions
+ */
+
+/* Rejecting mail */
+
+int sieve_action_reject_mail(const struct sieve_action_exec_env *aenv,
+ const struct smtp_address *recipient,
+ const char *reason);
+
+/*
+ * Mailbox
+ */
+
+// FIXME: move this to a more appropriate location
+bool sieve_mailbox_check_name(const char *mailbox, const char **error_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-address-parts.c b/pigeonhole/src/lib-sieve/sieve-address-parts.c
new file mode 100644
index 0000000..a856e20
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address-parts.c
@@ -0,0 +1,495 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "sieve-address-parts.h"
+
+#include <string.h>
+
+/*
+ * Default address parts
+ */
+
+const struct sieve_address_part_def *sieve_core_address_parts[] = {
+ &all_address_part, &local_address_part, &domain_address_part
+};
+
+const unsigned int sieve_core_address_parts_count =
+ N_ELEMENTS(sieve_core_address_parts);
+
+/*
+ * Address-part 'extension'
+ */
+
+static bool addrp_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def address_part_extension = {
+ .name = "@address-parts",
+ .validator_load = addrp_validator_load
+};
+
+/*
+ * Validator context:
+ * name-based address-part registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *adrp_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ adrp_ext = sieve_get_address_part_extension(svinst);
+ return sieve_validator_object_registry_get(valdtr, adrp_ext);
+}
+
+void sieve_address_part_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_address_part_def *addrp_def)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ sieve_validator_object_registry_add(regs, ext, &addrp_def->obj_def);
+}
+
+static bool sieve_address_part_exists
+(struct sieve_validator *valdtr, const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ return sieve_validator_object_registry_find(regs, identifier, NULL);
+}
+
+static const struct sieve_address_part *sieve_address_part_create_instance
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+ struct sieve_object object;
+ struct sieve_address_part *addrp;
+
+ if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+ return NULL;
+
+ addrp = p_new(sieve_command_pool(cmd), struct sieve_address_part, 1);
+ addrp->object = object;
+ addrp->def = (const struct sieve_address_part_def *) object.def;
+
+ return addrp;
+}
+
+static bool addrp_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ struct sieve_validator_object_registry *regs =
+ sieve_validator_object_registry_init(valdtr, ext);
+ unsigned int i;
+
+ /* Register core address-parts */
+ for ( i = 0; i < sieve_core_address_parts_count; i++ ) {
+ sieve_validator_object_registry_add
+ (regs, NULL, &(sieve_core_address_parts[i]->obj_def));
+ }
+
+ return TRUE;
+}
+
+void sieve_address_parts_link_tags
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+ int id_code)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *adrp_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ adrp_ext = sieve_get_address_part_extension(svinst);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, adrp_ext, &address_part_tag, id_code);
+}
+
+/*
+ * Address-part tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_address_part_is_instance_of
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_address_part_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_address_part_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def address_part_tag = {
+ .identifier = "ADDRESS-PART",
+ .is_instance_of = tag_address_part_is_instance_of,
+ .validate = tag_address_part_validate,
+ .generate = tag_address_part_generate
+};
+
+/* Argument implementation */
+
+static bool tag_address_part_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext ATTR_UNUSED, const char *identifier,
+ void **data)
+{
+ const struct sieve_address_part *addrp;
+
+ if ( data == NULL )
+ return sieve_address_part_exists(valdtr, identifier);
+
+ if ( (addrp=sieve_address_part_create_instance
+ (valdtr, cmd, identifier)) == NULL )
+ return FALSE;
+
+ *data = (void *) addrp;
+ return TRUE;
+}
+
+static bool tag_address_part_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ /* NOTE: Currenly trivial, but might need to allow for further validation for
+ * future extensions.
+ */
+
+ /* Syntax:
+ * ":localpart" / ":domain" / ":all" (subject to extension)
+ */
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+static bool tag_address_part_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ struct sieve_address_part *addrp =
+ (struct sieve_address_part *) arg->argument->data;
+
+ sieve_opr_address_part_emit(cgenv->sblock, addrp);
+
+ return TRUE;
+}
+
+/*
+ * Address-part operand
+ */
+
+const struct sieve_operand_class sieve_address_part_operand_class =
+ { "address part" };
+
+static const struct sieve_extension_objects core_address_parts =
+ SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_address_parts);
+
+const struct sieve_operand_def address_part_operand = {
+ .name = "address-part",
+ .code = SIEVE_OPERAND_ADDRESS_PART,
+ .class = &sieve_address_part_operand_class,
+ .interface = &core_address_parts
+};
+
+/*
+ * Address-part string list
+ */
+
+static int sieve_address_part_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_address_part_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int sieve_address_part_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+static void sieve_address_part_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace);
+
+struct sieve_address_part_stringlist {
+ struct sieve_stringlist strlist;
+
+ const struct sieve_address_part *addrp;
+ struct sieve_address_list *addresses;
+};
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+(const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+ struct sieve_address_list *addresses)
+{
+ struct sieve_address_part_stringlist *strlist;
+
+ strlist = t_new(struct sieve_address_part_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.next_item = sieve_address_part_stringlist_next_item;
+ strlist->strlist.reset = sieve_address_part_stringlist_reset;
+ strlist->strlist.get_length = sieve_address_part_stringlist_get_length;
+ strlist->strlist.set_trace = sieve_address_part_stringlist_set_trace;
+
+ strlist->addrp = addrp;
+ strlist->addresses = addresses;
+
+ return &strlist->strlist;
+}
+
+static int sieve_address_part_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct sieve_address_part_stringlist *strlist =
+ (struct sieve_address_part_stringlist *)_strlist;
+ struct smtp_address item;
+ string_t *item_unparsed;
+ int ret;
+
+ *str_r = NULL;
+
+ while ( *str_r == NULL ) {
+ if ( (ret=sieve_address_list_next_item
+ (strlist->addresses, &item, &item_unparsed)) <= 0 )
+ return ret;
+
+ if ( item.localpart == NULL ) {
+ if ( item_unparsed != NULL ) {
+ if ( _strlist->trace ) {
+ sieve_runtime_trace(_strlist->runenv, 0,
+ "extracting `%s' part from non-address value `%s'",
+ sieve_address_part_name(strlist->addrp),
+ str_sanitize(str_c(item_unparsed), 80));
+ }
+
+ if ( str_len(item_unparsed) == 0 ||
+ sieve_address_part_is(strlist->addrp, all_address_part) )
+ *str_r = item_unparsed;
+ }
+ } else {
+ const struct sieve_address_part *addrp = strlist->addrp;
+ const char *part = NULL;
+
+ if ( _strlist->trace ) {
+ sieve_runtime_trace(_strlist->runenv, 0,
+ "extracting `%s' part from address %s",
+ sieve_address_part_name(strlist->addrp),
+ smtp_address_encode_path(&item));
+ }
+
+ if ( addrp->def != NULL && addrp->def->extract_from != NULL )
+ part = addrp->def->extract_from(addrp, &item);
+
+ if ( part != NULL )
+ *str_r = t_str_new_const(part, strlen(part));
+ }
+ }
+
+ return 1;
+}
+
+static void sieve_address_part_stringlist_reset
+ (struct sieve_stringlist *_strlist)
+{
+ struct sieve_address_part_stringlist *strlist =
+ (struct sieve_address_part_stringlist *)_strlist;
+
+ sieve_address_list_reset(strlist->addresses);
+}
+
+static int sieve_address_part_stringlist_get_length
+ (struct sieve_stringlist *_strlist)
+{
+ struct sieve_address_part_stringlist *strlist =
+ (struct sieve_address_part_stringlist *)_strlist;
+
+ return sieve_address_list_get_length(strlist->addresses);
+}
+
+static void sieve_address_part_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+ struct sieve_address_part_stringlist *strlist =
+ (struct sieve_address_part_stringlist *)_strlist;
+
+ sieve_address_list_set_trace(strlist->addresses, trace);
+}
+
+/*
+ * Default ADDRESS-PART, MATCH-TYPE, COMPARATOR access
+ */
+
+int sieve_addrmatch_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while ( opok ) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 )
+ return opt;
+
+ switch ( *opt_code ) {
+ case SIEVE_MATCH_OPT_COMPARATOR:
+ opok = sieve_opr_comparator_dump(denv, address);
+ break;
+ case SIEVE_MATCH_OPT_MATCH_TYPE:
+ opok = sieve_opr_match_type_dump(denv, address);
+ break;
+ case SIEVE_AM_OPT_ADDRESS_PART:
+ opok = sieve_opr_address_part_dump(denv, address);
+ break;
+ default:
+ return ( final ? -1 : 1 );
+ }
+ }
+
+ return -1;
+}
+
+int sieve_addrmatch_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code, int *exec_status, struct sieve_address_part *addrp,
+ struct sieve_match_type *mtch, struct sieve_comparator *cmp)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE;
+ int status = SIEVE_EXEC_OK;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_OK;
+
+ while ( status == SIEVE_EXEC_OK ) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ){
+ if ( opt < 0 && exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return opt;
+ }
+
+ switch ( *opt_code ) {
+ case SIEVE_MATCH_OPT_COMPARATOR:
+ if (cmp == NULL) {
+ sieve_runtime_trace_error(renv, "unexpected comparator operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ status = sieve_opr_comparator_read(renv, address, cmp);
+ break;
+ case SIEVE_MATCH_OPT_MATCH_TYPE:
+ if (mtch == NULL) {
+ sieve_runtime_trace_error(renv, "unexpected match-type operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ status = sieve_opr_match_type_read(renv, address, mtch);
+ break;
+ case SIEVE_AM_OPT_ADDRESS_PART:
+ if (addrp == NULL) {
+ sieve_runtime_trace_error(renv, "unexpected address-part operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ status = sieve_opr_address_part_read(renv, address, addrp);
+ break;
+ default:
+ if ( final ) {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = status;
+ return -1;
+}
+
+/*
+ * Core address-part modifiers
+ */
+
+static const char *addrp_all_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+ const struct smtp_address *address)
+{
+ if ( address->localpart == NULL )
+ return NULL;
+ return smtp_address_encode(address);
+}
+
+static const char *addrp_domain_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+ const struct smtp_address *address)
+{
+ return address->domain;
+}
+
+static const char *addrp_localpart_extract_from
+(const struct sieve_address_part *addrp ATTR_UNUSED,
+ const struct smtp_address *address)
+{
+ return address->localpart;
+}
+
+const struct sieve_address_part_def all_address_part = {
+ SIEVE_OBJECT("all",
+ &address_part_operand, SIEVE_ADDRESS_PART_ALL),
+ .extract_from = addrp_all_extract_from
+};
+
+const struct sieve_address_part_def local_address_part = {
+ SIEVE_OBJECT("localpart",
+ &address_part_operand, SIEVE_ADDRESS_PART_LOCAL),
+ .extract_from = addrp_localpart_extract_from
+};
+
+const struct sieve_address_part_def domain_address_part = {
+ SIEVE_OBJECT("domain",
+ &address_part_operand, SIEVE_ADDRESS_PART_DOMAIN),
+ .extract_from = addrp_domain_extract_from
+};
+
diff --git a/pigeonhole/src/lib-sieve/sieve-address-parts.h b/pigeonhole/src/lib-sieve/sieve-address-parts.h
new file mode 100644
index 0000000..c774dc3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address-parts.h
@@ -0,0 +1,135 @@
+#ifndef SIEVE_ADDRESS_PARTS_H
+#define SIEVE_ADDRESS_PARTS_H
+
+#include "message-address.h"
+
+#include "sieve-common.h"
+#include "sieve-match.h"
+#include "sieve-extensions.h"
+#include "sieve-objects.h"
+
+/*
+ * Address part definition
+ */
+
+struct sieve_address_part_def {
+ struct sieve_object_def obj_def;
+
+ const char *(*extract_from)
+ (const struct sieve_address_part *addrp,
+ const struct smtp_address *address);
+};
+
+/*
+ * Address part instance
+ */
+
+struct sieve_address_part {
+ struct sieve_object object;
+
+ const struct sieve_address_part_def *def;
+};
+
+#define SIEVE_ADDRESS_PART_DEFAULT(definition) \
+ { SIEVE_OBJECT_DEFAULT(definition), &(definition) };
+
+#define sieve_address_part_name(addrp) \
+ ( (addrp)->object.def->identifier )
+#define sieve_address_part_is(addrp, definition) \
+ ( (addrp)->def == &(definition) )
+
+/*
+ * Core address parts
+ */
+
+enum sieve_address_part_code {
+ SIEVE_ADDRESS_PART_ALL,
+ SIEVE_ADDRESS_PART_LOCAL,
+ SIEVE_ADDRESS_PART_DOMAIN,
+ SIEVE_ADDRESS_PART_CUSTOM
+};
+
+extern const struct sieve_address_part_def all_address_part;
+extern const struct sieve_address_part_def local_address_part;
+extern const struct sieve_address_part_def domain_address_part;
+
+/*
+ * Address part tagged argument
+ */
+
+extern const struct sieve_argument_def address_part_tag;
+
+void sieve_address_parts_link_tags
+ (struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+ int id_code);
+
+/*
+ * Address part registry
+ */
+
+void sieve_address_part_register
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_address_part_def *addrp);
+
+/*
+ * Address part operand
+ */
+
+extern const struct sieve_operand_def address_part_operand;
+extern const struct sieve_operand_class sieve_address_part_operand_class;
+
+#define SIEVE_EXT_DEFINE_ADDRESS_PART(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_ADDRESS_PARTS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+static inline void sieve_opr_address_part_emit
+(struct sieve_binary_block *sblock, const struct sieve_address_part *addrp)
+{
+ sieve_opr_object_emit(sblock, addrp->object.ext, addrp->object.def);
+}
+
+static inline bool sieve_opr_address_part_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ return sieve_opr_object_dump
+ (denv, &sieve_address_part_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_address_part_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_address_part *addrp)
+{
+ if ( !sieve_opr_object_read
+ (renv, &sieve_address_part_operand_class, address, &addrp->object) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ addrp->def = (const struct sieve_address_part_def *) addrp->object.def;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Address-part string list
+ */
+
+struct sieve_stringlist *sieve_address_part_stringlist_create
+ (const struct sieve_runtime_env *renv, const struct sieve_address_part *addrp,
+ struct sieve_address_list *addresses);
+
+/*
+ * Match utility
+ */
+
+enum sieve_addrmatch_opt_operand {
+ SIEVE_AM_OPT_ADDRESS_PART = SIEVE_MATCH_OPT_LAST,
+ SIEVE_AM_OPT_LAST
+};
+
+int sieve_addrmatch_opr_optional_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code);
+
+int sieve_addrmatch_opr_optional_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code, int *exec_status, struct sieve_address_part *addrp,
+ struct sieve_match_type *mtch, struct sieve_comparator *cmp);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-address-source.c b/pigeonhole/src/lib-sieve/sieve-address-source.c
new file mode 100644
index 0000000..a5ec6f6
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address-source.c
@@ -0,0 +1,119 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+
+#include "sieve-address-source.h"
+
+bool sieve_address_source_parse
+(pool_t pool, const char *value,
+ struct sieve_address_source *asrc)
+{
+ struct smtp_address *address;
+ const char *error;
+ size_t val_len;
+
+ i_zero(asrc);
+
+ value = t_str_trim(value, "\t ");
+ value = t_str_lcase(value);
+ val_len = strlen(value);
+ if ( val_len > 0 ) {
+ if ( strcmp(value, "default") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+ } else if ( strcmp(value, "sender") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_SENDER;
+ } else if ( strcmp(value, "recipient") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_RECIPIENT;
+ } else if ( strcmp(value, "orig_recipient") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT;
+ } else if ( strcmp(value, "user_email") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_USER_EMAIL;
+ } else if ( strcmp(value, "postmaster") == 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_POSTMASTER;
+ } else if ( smtp_address_parse_path(pool_datastack_create(), value,
+ SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY, &address, &error) >= 0 ) {
+ asrc->type = SIEVE_ADDRESS_SOURCE_EXPLICIT;
+ asrc->address = smtp_address_clone(pool, address);
+ } else {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+bool sieve_address_source_parse_from_setting
+(struct sieve_instance *svinst, pool_t pool,
+ const char *setting, struct sieve_address_source *asrc)
+{
+ const char *value;
+
+ value = sieve_setting_get(svinst, setting);
+ if ( value == NULL )
+ return FALSE;
+
+ if ( !sieve_address_source_parse(pool, value, asrc) ) {
+ e_warning(svinst->event, "Invalid value for setting '%s': '%s'",
+ setting, value);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int sieve_address_source_get_address
+(struct sieve_address_source *asrc,
+ struct sieve_instance *svinst,
+ const struct sieve_script_env *senv,
+ struct sieve_message_context *msgctx,
+ enum sieve_execute_flags flags,
+ const struct smtp_address **addr_r)
+{
+ enum sieve_address_source_type type = asrc->type;
+
+ if ( type == SIEVE_ADDRESS_SOURCE_USER_EMAIL &&
+ svinst->user_email == NULL )
+ type = SIEVE_ADDRESS_SOURCE_RECIPIENT;
+
+ if ( (flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) != 0 ) {
+ switch ( type ) {
+ case SIEVE_ADDRESS_SOURCE_SENDER:
+ case SIEVE_ADDRESS_SOURCE_RECIPIENT:
+ case SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT:
+ /* We have no envelope */
+ type = SIEVE_ADDRESS_SOURCE_DEFAULT;
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch ( type ) {
+ case SIEVE_ADDRESS_SOURCE_SENDER:
+ *addr_r = sieve_message_get_sender(msgctx);
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_RECIPIENT:
+ *addr_r = sieve_message_get_final_recipient(msgctx);
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT:
+ *addr_r = sieve_message_get_orig_recipient(msgctx);
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_USER_EMAIL:
+ *addr_r = svinst->user_email;
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_POSTMASTER:
+ *addr_r = sieve_get_postmaster_smtp(senv);
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_EXPLICIT:
+ *addr_r = asrc->address;
+ return 1;
+ case SIEVE_ADDRESS_SOURCE_DEFAULT:
+ break;
+ }
+ return 0;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-address-source.h b/pigeonhole/src/lib-sieve/sieve-address-source.h
new file mode 100644
index 0000000..fac4b49
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address-source.h
@@ -0,0 +1,36 @@
+#ifndef SIEVE_ADDRESS_SOURCE_H
+#define SIEVE_ADDRESS_SOURCE_H
+
+#include "sieve-common.h"
+
+enum sieve_address_source_type {
+ SIEVE_ADDRESS_SOURCE_DEFAULT = 0,
+ SIEVE_ADDRESS_SOURCE_SENDER,
+ SIEVE_ADDRESS_SOURCE_RECIPIENT,
+ SIEVE_ADDRESS_SOURCE_ORIG_RECIPIENT,
+ SIEVE_ADDRESS_SOURCE_USER_EMAIL,
+ SIEVE_ADDRESS_SOURCE_POSTMASTER,
+ SIEVE_ADDRESS_SOURCE_EXPLICIT
+};
+
+struct sieve_address_source {
+ enum sieve_address_source_type type;
+ const struct smtp_address *address;
+};
+
+bool sieve_address_source_parse
+ (pool_t pool, const char *value,
+ struct sieve_address_source *asrc);
+bool sieve_address_source_parse_from_setting
+ (struct sieve_instance *svinst, pool_t pool,
+ const char *setting, struct sieve_address_source *asrc);
+
+int sieve_address_source_get_address
+ (struct sieve_address_source *asrc,
+ struct sieve_instance *svinst,
+ const struct sieve_script_env *senv,
+ struct sieve_message_context *msgctx,
+ enum sieve_execute_flags flags,
+ const struct smtp_address **addr_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-address.c b/pigeonhole/src/lib-sieve/sieve-address.c
new file mode 100644
index 0000000..f9456f8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address.c
@@ -0,0 +1,558 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "rfc822-parser.h"
+#include "message-address.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-address.h"
+
+#include <ctype.h>
+
+/*
+ * Header address list
+ */
+
+/* Forward declarations */
+
+static int
+sieve_header_address_list_next_string_item(struct sieve_stringlist *_strlist,
+ string_t **str_r);
+static int
+sieve_header_address_list_next_item(struct sieve_address_list *_addrlist,
+ struct smtp_address *addr_r,
+ string_t **unparsed_r);
+static void
+sieve_header_address_list_reset(struct sieve_stringlist *_strlist);
+static void
+sieve_header_address_list_set_trace(struct sieve_stringlist *_strlist,
+ bool trace);
+
+/* Stringlist object */
+
+struct sieve_header_address_list {
+ struct sieve_address_list addrlist;
+
+ struct sieve_stringlist *field_values;
+ const struct message_address *cur_address;
+};
+
+struct sieve_address_list *
+sieve_header_address_list_create(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_values)
+{
+ struct sieve_header_address_list *addrlist;
+
+ addrlist = t_new(struct sieve_header_address_list, 1);
+ addrlist->addrlist.strlist.runenv = renv;
+ addrlist->addrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ addrlist->addrlist.strlist.next_item =
+ sieve_header_address_list_next_string_item;
+ addrlist->addrlist.strlist.reset = sieve_header_address_list_reset;
+ addrlist->addrlist.strlist.set_trace =
+ sieve_header_address_list_set_trace;
+ addrlist->addrlist.next_item = sieve_header_address_list_next_item;
+ addrlist->field_values = field_values;
+
+ return &addrlist->addrlist;
+}
+
+static int
+sieve_header_address_list_next_address(
+ struct sieve_header_address_list *addrlist, struct smtp_address *addr_r)
+{
+ struct smtp_address adummy;
+ int ret = 0;
+
+ if (addr_r == NULL)
+ addr_r = &adummy;
+
+ while (addrlist->cur_address != NULL) {
+ const struct message_address *aitem = addrlist->cur_address;
+
+ addrlist->cur_address = addrlist->cur_address->next;
+
+ if (!aitem->invalid_syntax && aitem->domain != NULL &&
+ smtp_address_init_from_msg(addr_r, aitem) >= 0)
+ return 1;
+ ret = -1;
+ }
+ return ret;
+}
+
+static int
+sieve_header_address_list_next_item(struct sieve_address_list *_addrlist,
+ struct smtp_address *addr_r,
+ string_t **unparsed_r)
+{
+ struct sieve_header_address_list *addrlist =
+ (struct sieve_header_address_list *)_addrlist;
+ const struct sieve_runtime_env *runenv = _addrlist->strlist.runenv;
+ string_t *value_item = NULL;
+ bool trace = _addrlist->strlist.trace;
+
+ if (addr_r != NULL)
+ smtp_address_init(addr_r, NULL, NULL);
+ if (unparsed_r != NULL)
+ *unparsed_r = NULL;
+
+ for (;;) {
+ int ret;
+
+ if ((ret = sieve_header_address_list_next_address(
+ addrlist, addr_r)) < 0 &&
+ value_item != NULL) {
+ /* completely invalid address list is returned as-is */
+ if (trace) {
+ sieve_runtime_trace(
+ runenv, 0,
+ "invalid address value `%s'",
+ str_sanitize(str_c(value_item), 80));
+ }
+ if (unparsed_r != NULL)
+ *unparsed_r = value_item;
+ return 1;
+ }
+ if (ret > 0) {
+ if (trace) {
+ sieve_runtime_trace(
+ runenv, 0,
+ "address value `%s'",
+ str_sanitize(smtp_address_encode(addr_r),
+ 80));
+ }
+ return 1;
+ }
+
+ /* Read next header value from source list */
+ if ((ret = sieve_stringlist_next_item(addrlist->field_values,
+ &value_item)) <= 0)
+ return ret;
+ if (str_len(value_item) == 0) {
+ /* empty header value is returned as-is */
+ if (trace) {
+ sieve_runtime_trace(runenv, 0,
+ "empty address value");
+ }
+ addrlist->cur_address = NULL;
+ if (unparsed_r != NULL)
+ *unparsed_r = value_item;
+ return 1;
+ }
+
+ if (trace) {
+ sieve_runtime_trace(
+ runenv, 0,
+ "parsing address header value `%s'",
+ str_sanitize(str_c(value_item), 80));
+ }
+
+ addrlist->cur_address = message_address_parse(
+ pool_datastack_create(),
+ (const unsigned char *)str_data(value_item),
+ str_len(value_item), 256, 0);
+ }
+ i_unreached();
+}
+
+static int
+sieve_header_address_list_next_string_item(struct sieve_stringlist *_strlist,
+ string_t **str_r)
+{
+ struct sieve_address_list *addrlist =
+ (struct sieve_address_list *)_strlist;
+ struct smtp_address addr;
+ int ret;
+
+ if ((ret = sieve_header_address_list_next_item(
+ addrlist, &addr, str_r)) <= 0)
+ return ret;
+
+ if (addr.localpart != NULL) {
+ const char *addr_str = smtp_address_encode(&addr);
+
+ if (str_r != NULL)
+ *str_r = t_str_new_const(addr_str, strlen(addr_str));
+ }
+ return 1;
+}
+
+static void sieve_header_address_list_reset(struct sieve_stringlist *_strlist)
+{
+ struct sieve_header_address_list *addrlist =
+ (struct sieve_header_address_list *)_strlist;
+
+ sieve_stringlist_reset(addrlist->field_values);
+ addrlist->cur_address = NULL;
+}
+
+static void
+sieve_header_address_list_set_trace(struct sieve_stringlist *_strlist,
+ bool trace)
+{
+ struct sieve_header_address_list *addrlist =
+ (struct sieve_header_address_list *)_strlist;
+
+ sieve_stringlist_set_trace(addrlist->field_values, trace);
+}
+
+/*
+ * RFC 2822 addresses
+ */
+
+/* Mail message address according to RFC 2822 and implemented in the Dovecot
+ message address parser:
+
+ address = mailbox / group
+ mailbox = name-addr / addr-spec
+ name-addr = [display-name] angle-addr
+ angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
+ group = display-name ":" [mailbox-list / CFWS] ";" [CFWS]
+ display-name = phrase
+
+ addr-spec = local-part "@" domain
+ local-part = dot-atom / quoted-string / obs-local-part
+ domain = dot-atom / domain-literal / obs-domain
+ domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+ dcontent = dtext / quoted-pair
+ dtext = NO-WS-CTL / ; Non white space controls
+ %d33-90 / ; The rest of the US-ASCII
+ %d94-126 ; characters not including "[",
+ ; "]", or "\"
+
+ atext = ALPHA / DIGIT / ; Any character except controls,
+ "!" / "#" / ; SP, and specials.
+ "$" / "%" / ; Used for atoms
+ "&" / "'" /
+ "*" / "+" /
+ "-" / "/" /
+ "=" / "?" /
+ "^" / "_" /
+ "`" / "{" /
+ "|" / "}" /
+ "~"
+ atom = [CFWS] 1*atext [CFWS]
+ dot-atom = [CFWS] dot-atom-text [CFWS]
+ dot-atom-text = 1*atext *("." 1*atext)
+ word = atom / quoted-string
+ phrase = 1*word / obs-phrase
+
+ Message address specification as allowed bij the RFC 5228 SIEVE
+ specification:
+ sieve-address = addr-spec ; simple address
+ / phrase "<" addr-spec ">" ; name & addr-spec\
+
+ Which concisely is about equal to:
+ sieve-address = mailbox
+ */
+
+/*
+ * Address parse context
+ */
+
+struct sieve_message_address_parser {
+ struct rfc822_parser_context parser;
+
+ string_t *str;
+ string_t *local_part;
+ string_t *domain;
+
+ string_t *error;
+};
+
+/*
+ * Error handling
+ */
+
+static inline void ATTR_FORMAT(2, 3)
+sieve_address_error(struct sieve_message_address_parser *ctx,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ if (str_len(ctx->error) == 0) {
+ va_start(args, fmt);
+ str_vprintfa(ctx->error, fmt, args);
+ va_end(args);
+ }
+}
+
+/*
+ Partial RFC 2822 address parser
+
+ FIXME: lots of overlap with dovecot/src/lib-mail/message-parser.c
+ --> this implementation adds textual error reporting
+ MERGE!
+ */
+
+static int check_local_part(struct sieve_message_address_parser *ctx)
+{
+ const unsigned char *p, *pend;
+
+ p = str_data(ctx->local_part);
+ pend = p + str_len(ctx->local_part);
+ while (p < pend) {
+ if (*p < 0x20 || *p > 0x7e)
+ return -1;
+ p++;
+ }
+ return 0;
+}
+
+static int parse_local_part(struct sieve_message_address_parser *ctx)
+{
+ int ret;
+
+ /*
+ local-part = dot-atom / quoted-string / obs-local-part
+ obs-local-part = word *("." word)
+ */
+ if (ctx->parser.data == ctx->parser.end) {
+ sieve_address_error(ctx, "empty local part");
+ return -1;
+ }
+
+ str_truncate(ctx->local_part, 0);
+ if (*ctx->parser.data == '"') {
+ ret = rfc822_parse_quoted_string(&ctx->parser, ctx->local_part);
+ } else {
+ ret = -1;
+ /* NOTE: this deviates from dot-atom syntax to allow some
+ Japanese mail addresses with dots at non-standard places to
+ be accepted. */
+ do {
+ while (*ctx->parser.data == '.') {
+ str_append_c(ctx->local_part, '.');
+ ctx->parser.data++;
+ if (ctx->parser.data == ctx->parser.end) {
+ /* @domain is missing, but local-part
+ parsing was successful */
+ return 0;
+ }
+ ret = 1;
+ }
+ if (*ctx->parser.data == '@')
+ break;
+ ret = rfc822_parse_atom(&ctx->parser, ctx->local_part);
+ } while (ret > 0 && *ctx->parser.data == '.');
+ }
+
+ if (ret < 0 || check_local_part(ctx) < 0) {
+ sieve_address_error(ctx, "invalid local part");
+ return -1;
+ }
+ return ret;
+}
+
+static int parse_domain(struct sieve_message_address_parser *ctx)
+{
+ int ret;
+
+ str_truncate(ctx->domain, 0);
+ if ((ret = rfc822_parse_domain(&ctx->parser, ctx->domain)) < 0) {
+ sieve_address_error(ctx, "invalid or missing domain");
+ return -1;
+ }
+
+ return ret;
+}
+
+static int parse_addr_spec(struct sieve_message_address_parser *ctx)
+{
+ /* addr-spec = local-part "@" domain */
+ int ret;
+
+ if ((ret = parse_local_part(ctx)) < 0)
+ return ret;
+
+ if (ret > 0 && *ctx->parser.data == '@') {
+ return parse_domain(ctx);
+ }
+
+ sieve_address_error(
+ ctx, "invalid or lonely local part '%s' (expecting '@')",
+ str_sanitize(str_c(ctx->local_part), 80));
+ return -1;
+}
+
+static int parse_mailbox(struct sieve_message_address_parser *ctx)
+{
+ int ret;
+ const unsigned char *start;
+
+ /* sieve-address = addr-spec ; simple address
+ / phrase "<" addr-spec ">" ; name & addr-spec
+ */
+
+ /* Record parser state in case we fail at our first attempt */
+ start = ctx->parser.data;
+
+ /* First try: phrase "<" addr-spec ">" ; name & addr-spec */
+ str_truncate(ctx->str, 0);
+ if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
+ *ctx->parser.data != '<') {
+ /* Failed; try just bare addr-spec */
+ ctx->parser.data = start;
+ return parse_addr_spec(ctx);
+ }
+
+ /* "<" addr-spec ">" */
+ ctx->parser.data++;
+
+ if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0) {
+ if (ret < 0)
+ sieve_address_error(ctx, "invalid characters after <");
+ return ret;
+ }
+
+ if (parse_addr_spec(ctx) < 0)
+ return -1;
+
+ if (*ctx->parser.data != '>') {
+ sieve_address_error(ctx, "missing '>'");
+ return -1;
+ }
+ ctx->parser.data++;
+
+ if ((ret = rfc822_skip_lwsp(&ctx->parser)) < 0) {
+ sieve_address_error(
+ ctx, "address ends with invalid characters");
+ }
+ return ret;
+}
+
+static bool
+parse_mailbox_address(struct sieve_message_address_parser *ctx,
+ const unsigned char *address, unsigned int addr_size)
+{
+ /* Initialize parser */
+
+ rfc822_parser_init(&ctx->parser, address, addr_size, NULL);
+
+ /* Parse */
+
+ rfc822_skip_lwsp(&ctx->parser);
+
+ if (ctx->parser.data == ctx->parser.end) {
+ sieve_address_error(ctx, "empty address");
+ return FALSE;
+ }
+
+ if (parse_mailbox(ctx) < 0)
+ return FALSE;
+
+ if (ctx->parser.data != ctx->parser.end) {
+ if (*ctx->parser.data == ',') {
+ sieve_address_error(
+ ctx, "not a single addres (found ',')");
+ } else {
+ sieve_address_error(
+ ctx, "address ends in invalid characters");
+ }
+ return FALSE;
+ }
+
+ if (str_len(ctx->domain) == 0) {
+ /* Not gonna happen */
+ sieve_address_error(ctx, "missing domain");
+ return FALSE;
+ }
+
+ if (str_len(ctx->local_part) == 0) {
+ sieve_address_error(ctx, "missing local part");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+sieve_address_do_validate(const unsigned char *address, size_t size,
+ const char **error_r)
+{
+ struct sieve_message_address_parser ctx;
+
+ *error_r = NULL;
+
+ if (address == NULL) {
+ *error_r = "null address";
+ return FALSE;
+ }
+
+ i_zero(&ctx);
+
+ ctx.local_part = t_str_new(128);
+ ctx.domain = t_str_new(128);
+ ctx.str = t_str_new(128);
+ ctx.error = t_str_new(128);
+
+ if (!parse_mailbox_address(&ctx, address, size)) {
+ *error_r = str_c(ctx.error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static const struct smtp_address *
+sieve_address_do_parse(const unsigned char *address, size_t size,
+ const char **error_r)
+{
+ struct sieve_message_address_parser ctx;
+
+ *error_r = NULL;
+
+ if (address == NULL)
+ return NULL;
+
+ i_zero(&ctx);
+
+ ctx.local_part = t_str_new(128);
+ ctx.domain = t_str_new(128);
+ ctx.str = t_str_new(128);
+ ctx.error = t_str_new(128);
+
+ if (!parse_mailbox_address(&ctx, address, size)) {
+ *error_r = str_c(ctx.error);
+ return NULL;
+ }
+
+ (void)str_lcase(str_c_modifiable(ctx.domain));
+
+ return smtp_address_create_temp(str_c(ctx.local_part),
+ str_c(ctx.domain));
+}
+
+/*
+ * Sieve address
+ */
+
+const struct smtp_address *
+sieve_address_parse(const char *address, const char **error_r)
+{
+ return sieve_address_do_parse((const unsigned char *)address,
+ strlen(address), error_r);
+}
+
+const struct smtp_address *
+sieve_address_parse_str(string_t *address, const char **error_r)
+{
+ return sieve_address_do_parse(str_data(address), str_len(address),
+ error_r);
+}
+
+bool sieve_address_validate(const char *address, const char **error_r)
+{
+ return sieve_address_do_validate((const unsigned char *)address,
+ strlen(address), error_r);
+}
+
+bool sieve_address_validate_str(string_t *address, const char **error_r)
+{
+ return sieve_address_do_validate(str_data(address), str_len(address),
+ error_r);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-address.h b/pigeonhole/src/lib-sieve/sieve-address.h
new file mode 100644
index 0000000..3779a07
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-address.h
@@ -0,0 +1,67 @@
+#ifndef SIEVE_ADDRESS_H
+#define SIEVE_ADDRESS_H
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "smtp-address.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
+/*
+ * Address list API
+ */
+
+struct sieve_address_list {
+ struct sieve_stringlist strlist;
+
+ int (*next_item)(struct sieve_address_list *_addrlist,
+ struct smtp_address *addr_r, string_t **unparsed_r);
+};
+
+static inline int
+sieve_address_list_next_item(struct sieve_address_list *addrlist,
+ struct smtp_address *addr_r, string_t **unparsed_r)
+{
+ return addrlist->next_item(addrlist, addr_r, unparsed_r);
+}
+
+static inline void
+sieve_address_list_reset(struct sieve_address_list *addrlist)
+{
+ sieve_stringlist_reset(&addrlist->strlist);
+}
+
+static inline int
+sieve_address_list_get_length(struct sieve_address_list *addrlist)
+{
+ return sieve_stringlist_get_length(&addrlist->strlist);
+}
+
+static inline void
+sieve_address_list_set_trace(struct sieve_address_list *addrlist, bool trace)
+{
+ sieve_stringlist_set_trace(&addrlist->strlist, trace);
+}
+
+/*
+ * Header address list
+ */
+
+struct sieve_address_list *
+sieve_header_address_list_create(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_values);
+
+/*
+ * Sieve address parsing/validatin
+ */
+
+const struct smtp_address *
+sieve_address_parse(const char *address, const char **error_r);
+const struct smtp_address *
+sieve_address_parse_str(string_t *address, const char **error_r);
+
+bool sieve_address_validate(const char *address, const char **error_r);
+bool sieve_address_validate_str(string_t *address, const char **error_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-ast.c b/pigeonhole/src/lib-sieve/sieve-ast.c
new file mode 100644
index 0000000..197dbad
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-ast.c
@@ -0,0 +1,1111 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-extensions.h"
+
+#include "sieve-ast.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+static struct sieve_ast_node *
+sieve_ast_node_create(struct sieve_ast *ast, struct sieve_ast_node *parent,
+ enum sieve_ast_type type, unsigned int source_line);
+
+/*
+ * Types
+ */
+
+/* Extensions to the AST */
+
+struct sieve_ast_extension_reg {
+ const struct sieve_extension *ext;
+ const struct sieve_ast_extension *ast_ext;
+ void *context;
+
+ bool required:1;
+};
+
+/*
+ * AST object
+ */
+
+struct sieve_ast {
+ pool_t pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+
+ struct sieve_script *script;
+
+ struct sieve_ast_node *root;
+
+ ARRAY(const struct sieve_extension *) linked_extensions;
+ ARRAY(struct sieve_ast_extension_reg) extensions;
+};
+
+struct sieve_ast *sieve_ast_create(struct sieve_script *script)
+{
+ pool_t pool;
+ struct sieve_ast *ast;
+ unsigned int ext_count;
+
+ pool = pool_alloconly_create("sieve_ast", 32768);
+ ast = p_new(pool, struct sieve_ast, 1);
+ ast->pool = pool;
+ ast->refcount = 1;
+
+ ast->script = script;
+ sieve_script_ref(script);
+ ast->svinst = sieve_script_svinst(script);
+
+ ast->root = sieve_ast_node_create(ast, NULL, SAT_ROOT, 0);
+ ast->root->identifier = "ROOT";
+
+ ext_count = sieve_extensions_get_count(ast->svinst);
+ p_array_init(&ast->linked_extensions, pool, ext_count);
+ p_array_init(&ast->extensions, pool, ext_count);
+
+ return ast;
+}
+
+void sieve_ast_ref(struct sieve_ast *ast)
+{
+ ast->refcount++;
+}
+
+void sieve_ast_unref(struct sieve_ast **ast)
+{
+ unsigned int i, ext_count;
+ const struct sieve_ast_extension_reg *extrs;
+
+ i_assert((*ast)->refcount > 0);
+
+ if (--(*ast)->refcount != 0)
+ return;
+
+ /* Release script reference */
+ sieve_script_unref(&(*ast)->script);
+
+ /* Signal registered extensions that the AST is being destroyed */
+ extrs = array_get(&(*ast)->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extrs[i].ast_ext != NULL && extrs[i].ast_ext->free != NULL)
+ extrs[i].ast_ext->free(extrs[i].ext, *ast,
+ extrs[i].context);
+ }
+
+ /* Destroy AST */
+ pool_unref(&(*ast)->pool);
+
+ *ast = NULL;
+}
+
+struct sieve_ast_node *sieve_ast_root(struct sieve_ast *ast)
+{
+ return ast->root;
+}
+
+pool_t sieve_ast_pool(struct sieve_ast *ast)
+{
+ return ast->pool;
+}
+
+struct sieve_script *sieve_ast_script(struct sieve_ast *ast)
+{
+ return ast->script;
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_ast_extension_link(struct sieve_ast *ast,
+ const struct sieve_extension *ext, bool required)
+{
+ unsigned int i, ext_count;
+ const struct sieve_extension *const *extensions;
+ struct sieve_ast_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ /* Initialize registration */
+ reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->required = reg->required || required;
+
+ /* Prevent duplicates */
+ extensions = array_get(&ast->linked_extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extensions[i] == ext)
+ return;
+ }
+
+ /* Add extension */
+ array_append(&ast->linked_extensions, &ext, 1);
+}
+
+const struct sieve_extension * const *
+sieve_ast_extensions_get(struct sieve_ast *ast, unsigned int *count_r)
+{
+ return array_get(&ast->linked_extensions, count_r);
+}
+
+void sieve_ast_extension_register(struct sieve_ast *ast,
+ const struct sieve_extension *ext,
+ const struct sieve_ast_extension *ast_ext,
+ void *context)
+{
+ struct sieve_ast_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ /* Initialize registration */
+ reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->ast_ext = ast_ext;
+ reg->context = context;
+}
+
+void sieve_ast_extension_set_context(struct sieve_ast *ast,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_ast_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
+ reg->context = context;
+}
+
+void *sieve_ast_extension_get_context(struct sieve_ast *ast,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_ast_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int)array_count(&ast->extensions))
+ return NULL;
+
+ reg = array_idx(&ast->extensions, (unsigned int)ext->id);
+
+ return reg->context;
+}
+
+bool sieve_ast_extension_is_required
+(struct sieve_ast *ast, const struct sieve_extension *ext)
+{
+ const struct sieve_ast_extension_reg *reg;
+
+ i_assert(ext->id >= 0 &&
+ ext->id < (int)array_count(&ast->extensions));
+
+ reg = array_idx(&ast->extensions, (unsigned int)ext->id);
+ return reg->required;
+}
+
+/*
+ * AST list implementations
+ */
+
+/* Very simplistic linked list implementation
+ FIXME: Merge with core
+ */
+#define __LIST_CREATE(pool, type) { \
+ type *list = p_new(pool, type, 1); \
+ list->head = NULL; \
+ list->tail = NULL; \
+ list->len = 0; \
+ return list; \
+ }
+
+#define __LIST_ADD(list, node) { \
+ if (list->len + 1 < list->len) \
+ return FALSE; \
+ \
+ node->next = NULL; \
+ if (list->head == NULL) { \
+ node->prev = NULL; \
+ list->head = node; \
+ list->tail = node; \
+ } else { \
+ list->tail->next = node; \
+ node->prev = list->tail; \
+ list->tail = node; \
+ } \
+ list->len++; \
+ node->list = list; \
+ return TRUE; \
+ }
+
+#define __LIST_INSERT(list, before, node) { \
+ if (list->len + 1 < list->len) \
+ return FALSE; \
+ \
+ node->next = before; \
+ if (list->head == before) { \
+ node->prev = NULL; \
+ list->head = node; \
+ } else { \
+ before->prev->next = node; \
+ } \
+ node->prev = before->prev; \
+ before->prev = node; \
+ list->len++; \
+ node->list = list; \
+ \
+ return TRUE; \
+ }
+
+#define __LIST_JOIN(list, node_type, items) { \
+ node_type *node; \
+ \
+ if (list->len + items->len < list->len) \
+ return FALSE; \
+ \
+ if (items->len == 0) \
+ return TRUE; \
+ \
+ if (list->head == NULL) { \
+ list->head = items->head; \
+ list->tail = items->tail; \
+ } else { \
+ list->tail->next = items->head; \
+ items->head->prev = list->tail; \
+ list->tail = items->tail; \
+ } \
+ list->len += items->len; \
+ \
+ node = items->head; \
+ while (node != NULL) { \
+ node->list = list; \
+ node = node->next; \
+ } \
+ return TRUE; \
+ }
+
+#define __LIST_DETACH(first, node_type, count) { \
+ node_type *last, *result; \
+ unsigned int left; \
+ \
+ i_assert(first->list != NULL); \
+ \
+ left = count - 1; \
+ last = first; \
+ while (left > 0 && last->next != NULL) { \
+ left--; \
+ last = last->next; \
+ } \
+ \
+ if (first->list->head == first) \
+ first->list->head = last->next; \
+ if (first->list->tail == last) \
+ first->list->tail = first->prev; \
+ \
+ if (first->prev != NULL) \
+ first->prev->next = last->next; \
+ if (last->next != NULL) \
+ last->next->prev = first->prev; \
+ \
+ first->list->len -= count - left; \
+ \
+ result = last->next; \
+ first->prev = NULL; \
+ last->next = NULL; \
+ \
+ return result; \
+ }
+
+/* List of AST nodes */
+
+static struct sieve_ast_list *
+sieve_ast_list_create(pool_t pool)
+ __LIST_CREATE(pool, struct sieve_ast_list)
+
+static bool
+sieve_ast_list_add(struct sieve_ast_list *list, struct sieve_ast_node *node)
+ __LIST_ADD(list, node)
+
+static struct sieve_ast_node *
+sieve_ast_list_detach(struct sieve_ast_node *first, unsigned int count)
+ __LIST_DETACH(first, struct sieve_ast_node, count)
+
+/* List of argument AST nodes */
+
+struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool)
+ __LIST_CREATE(pool, struct sieve_ast_arg_list)
+
+bool sieve_ast_arg_list_add(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *argument)
+ __LIST_ADD(list, argument)
+
+bool sieve_ast_arg_list_insert(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *before,
+ struct sieve_ast_argument *argument)
+ __LIST_INSERT(list, before, argument)
+
+static bool
+sieve_ast_arg_list_join(struct sieve_ast_arg_list *list,
+ struct sieve_ast_arg_list *items)
+ __LIST_JOIN(list, struct sieve_ast_argument, items)
+
+static struct sieve_ast_argument *
+sieve_ast_arg_list_detach(struct sieve_ast_argument *first,
+ const unsigned int count)
+ __LIST_DETACH(first, struct sieve_ast_argument, count)
+
+void sieve_ast_arg_list_substitute(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *argument,
+ struct sieve_ast_argument *replacement)
+{
+ if (list->head == argument)
+ list->head = replacement;
+ if (list->tail == argument)
+ list->tail = replacement;
+
+ if (argument->prev != NULL)
+ argument->prev->next = replacement;
+ if (argument->next != NULL)
+ argument->next->prev = replacement;
+
+ replacement->prev = argument->prev;
+ replacement->next = argument->next;
+ replacement->list = argument->list;
+
+ argument->next = NULL;
+ argument->prev = NULL;
+}
+
+/*
+ * AST node
+ */
+
+static struct sieve_ast_node *
+sieve_ast_node_create(struct sieve_ast *ast, struct sieve_ast_node *parent,
+ enum sieve_ast_type type, unsigned int source_line)
+{
+ struct sieve_ast_node *node =
+ p_new(ast->pool, struct sieve_ast_node, 1);
+
+ node->ast = ast;
+ node->parent = parent;
+ node->type = type;
+
+ node->prev = NULL;
+ node->next = NULL;
+
+ node->arguments = NULL;
+ node->tests = NULL;
+ node->commands = NULL;
+
+ node->test_list = FALSE;
+ node->block = FALSE;
+
+ node->source_line = source_line;
+
+ return node;
+}
+
+static bool
+sieve_ast_node_add_command(struct sieve_ast_node *node,
+ struct sieve_ast_node *command)
+{
+ i_assert(command->type == SAT_COMMAND &&
+ (node->type == SAT_ROOT || node->type == SAT_COMMAND));
+
+ if (node->commands == NULL)
+ node->commands = sieve_ast_list_create(node->ast->pool);
+
+ return sieve_ast_list_add(node->commands, command);
+}
+
+static bool
+sieve_ast_node_add_test(struct sieve_ast_node *node,
+ struct sieve_ast_node *test)
+{
+ i_assert(test->type == SAT_TEST &&
+ (node->type == SAT_TEST || node->type == SAT_COMMAND));
+
+ if (node->tests == NULL)
+ node->tests = sieve_ast_list_create(node->ast->pool);
+
+ return sieve_ast_list_add(node->tests, test);
+}
+
+static bool
+sieve_ast_node_add_argument(struct sieve_ast_node *node,
+ struct sieve_ast_argument *argument)
+{
+ i_assert(node->type == SAT_TEST || node->type == SAT_COMMAND);
+
+ if (node->arguments == NULL)
+ node->arguments = sieve_ast_arg_list_create(node->ast->pool);
+
+ return sieve_ast_arg_list_add(node->arguments, argument);
+}
+
+struct sieve_ast_node *sieve_ast_node_detach(struct sieve_ast_node *first)
+{
+ return sieve_ast_list_detach(first, 1);
+}
+
+const char *sieve_ast_type_name(enum sieve_ast_type ast_type)
+{
+ switch (ast_type) {
+ case SAT_NONE:
+ return "none";
+ case SAT_ROOT:
+ return "ast root node";
+ case SAT_COMMAND:
+ return "command";
+ case SAT_TEST:
+ return "test";
+ default:
+ return "??AST NODE??";
+ }
+}
+
+/*
+ * Argument AST node
+ */
+
+struct sieve_ast_argument *
+sieve_ast_argument_create(struct sieve_ast *ast, unsigned int source_line)
+{
+ struct sieve_ast_argument *arg =
+ p_new(ast->pool, struct sieve_ast_argument, 1);
+
+ arg->ast = ast;
+
+ arg->prev = NULL;
+ arg->next = NULL;
+
+ arg->source_line = source_line;
+
+ arg->argument = NULL;
+
+ return arg;
+}
+
+static void
+sieve_ast_argument_substitute(struct sieve_ast_argument *argument,
+ struct sieve_ast_argument *replacement)
+{
+ sieve_ast_arg_list_substitute(argument->list, argument, replacement);
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_string_create_raw(struct sieve_ast *ast, string_t *str,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(ast, source_line);
+
+ argument->type = SAAT_STRING;
+ argument->_value.str = str;
+
+ return argument;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_string_create(struct sieve_ast_node *node,
+ const string_t *str, unsigned int source_line)
+{
+ struct sieve_ast_argument *argument;
+ string_t *newstr;
+
+ /* Allocate new internal string buffer */
+ newstr = str_new(node->ast->pool, str_len(str));
+
+ /* Clone string */
+ str_append_str(newstr, str);
+
+ /* Create string argument */
+ argument = sieve_ast_argument_string_create_raw(
+ node->ast, newstr, source_line);
+
+ /* Add argument to command/test node */
+ sieve_ast_node_add_argument(node, argument);
+
+ return argument;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_cstring_create(struct sieve_ast_node *node, const char *str,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *argument;
+ string_t *newstr;
+
+ /* Allocate new internal string buffer */
+ newstr = str_new(node->ast->pool, strlen(str));
+
+ /* Clone string */
+ str_append(newstr, str);
+
+ /* Create string argument */
+ argument = sieve_ast_argument_string_create_raw(
+ node->ast, newstr, source_line);
+
+ /* Add argument to command/test node */
+ sieve_ast_node_add_argument(node, argument);
+
+ return argument;
+}
+
+void sieve_ast_argument_string_set(struct sieve_ast_argument *argument,
+ string_t *newstr)
+{
+ i_assert(argument->type == SAAT_STRING);
+ argument->_value.str = newstr;
+}
+
+void sieve_ast_argument_string_setc(struct sieve_ast_argument *argument,
+ const char *newstr)
+{
+ i_assert(argument->type == SAAT_STRING);
+
+ str_truncate(argument->_value.str, 0);
+ str_append(argument->_value.str, newstr);
+}
+
+void sieve_ast_argument_number_substitute(struct sieve_ast_argument *argument,
+ sieve_number_t number)
+{
+ argument->type = SAAT_NUMBER;
+ argument->_value.number = number;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_create(struct sieve_ast_node *node,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(node->ast, source_line);
+
+ argument->type = SAAT_STRING_LIST;
+ argument->_value.strlist = NULL;
+
+ sieve_ast_node_add_argument(node, argument);
+
+ return argument;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_substitute(struct sieve_ast_node *node,
+ struct sieve_ast_argument *arg)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(node->ast, arg->source_line);
+
+ argument->type = SAAT_STRING_LIST;
+ argument->_value.strlist = NULL;
+
+ sieve_ast_argument_substitute(arg, argument);
+
+ return argument;
+}
+
+static inline bool
+_sieve_ast_stringlist_add_item(struct sieve_ast_argument *list,
+ struct sieve_ast_argument *item)
+{
+ i_assert(list->type == SAAT_STRING_LIST);
+
+ if (list->_value.strlist == NULL) {
+ list->_value.strlist =
+ sieve_ast_arg_list_create(list->ast->pool);
+ }
+
+ return sieve_ast_arg_list_add(list->_value.strlist, item);
+}
+
+static bool
+sieve_ast_stringlist_add_stringlist(struct sieve_ast_argument *list,
+ struct sieve_ast_argument *items)
+{
+ i_assert(list->type == SAAT_STRING_LIST);
+ i_assert(items->type == SAAT_STRING_LIST);
+
+ if (list->_value.strlist == NULL) {
+ list->_value.strlist =
+ sieve_ast_arg_list_create(list->ast->pool);
+ }
+
+ return sieve_ast_arg_list_join(list->_value.strlist,
+ items->_value.strlist);
+}
+
+static bool
+_sieve_ast_stringlist_add_str(struct sieve_ast_argument *list, string_t *str,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *stritem;
+
+ stritem = sieve_ast_argument_create(list->ast, source_line);
+ stritem->type = SAAT_STRING;
+ stritem->_value.str = str;
+
+ return _sieve_ast_stringlist_add_item(list, stritem);
+}
+
+bool sieve_ast_stringlist_add(struct sieve_ast_argument *list,
+ const string_t *str, unsigned int source_line)
+{
+ string_t *copied_str = str_new(list->ast->pool, str_len(str));
+ str_append_str(copied_str, str);
+
+ return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
+}
+
+bool sieve_ast_stringlist_add_strc(struct sieve_ast_argument *list,
+ const char *str, unsigned int source_line)
+{
+ string_t *copied_str = str_new(list->ast->pool, strlen(str));
+ str_append(copied_str, str);
+
+ return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_create(struct sieve_ast_node *node, const char *tag,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(node->ast, source_line);
+
+ argument->type = SAAT_TAG;
+ argument->_value.tag = p_strdup(node->ast->pool, tag);
+
+ if (!sieve_ast_node_add_argument(node, argument))
+ return NULL;
+ return argument;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_insert(struct sieve_ast_argument *before,
+ const char *tag, unsigned int source_line)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(before->ast, source_line);
+
+ argument->type = SAAT_TAG;
+ argument->_value.tag = p_strdup(before->ast->pool, tag);
+
+ if (!sieve_ast_arg_list_insert(before->list, before, argument))
+ return NULL;
+ return argument;
+}
+
+struct sieve_ast_argument *
+sieve_ast_argument_number_create(struct sieve_ast_node *node,
+ sieve_number_t number,
+ unsigned int source_line)
+{
+ struct sieve_ast_argument *argument =
+ sieve_ast_argument_create(node->ast, source_line);
+
+ argument->type = SAAT_NUMBER;
+ argument->_value.number = number;
+
+ if (!sieve_ast_node_add_argument(node, argument))
+ return NULL;
+ return argument;
+}
+
+void sieve_ast_argument_number_set(struct sieve_ast_argument *argument,
+ sieve_number_t newnum)
+{
+ i_assert(argument->type == SAAT_NUMBER);
+ argument->_value.number = newnum;
+}
+
+struct sieve_ast_argument *
+sieve_ast_arguments_detach(struct sieve_ast_argument *first,
+ unsigned int count)
+{
+ return sieve_ast_arg_list_detach(first, count);
+}
+
+bool sieve_ast_argument_attach(struct sieve_ast_node *node,
+ struct sieve_ast_argument *argument)
+{
+ return sieve_ast_node_add_argument(node, argument);
+}
+
+const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type)
+{
+ switch (arg_type) {
+ case SAAT_NONE:
+ return "none";
+ case SAAT_STRING_LIST:
+ return "a string list";
+ case SAAT_STRING:
+ return "a string";
+ case SAAT_NUMBER:
+ return "a number";
+ case SAAT_TAG:
+ return "a tag";
+ default:
+ return "??ARGUMENT??";
+ }
+}
+
+/* Test AST node */
+
+struct sieve_ast_node *
+sieve_ast_test_create(struct sieve_ast_node *parent, const char *identifier,
+ unsigned int source_line)
+{
+ struct sieve_ast_node *test = sieve_ast_node_create(
+ parent->ast, parent, SAT_TEST, source_line);
+
+ test->identifier = p_strdup(parent->ast->pool, identifier);
+
+ if (!sieve_ast_node_add_test(parent, test))
+ return NULL;
+ return test;
+}
+
+/* Command AST node */
+
+struct sieve_ast_node *
+sieve_ast_command_create(struct sieve_ast_node *parent, const char *identifier,
+ unsigned int source_line)
+{
+ struct sieve_ast_node *command = sieve_ast_node_create(
+ parent->ast, parent, SAT_COMMAND, source_line);
+
+ command->identifier = p_strdup(parent->ast->pool, identifier);
+
+ if (!sieve_ast_node_add_command(parent, command))
+ return NULL;
+ return command;
+}
+
+/*
+ * Utility
+ */
+
+int sieve_ast_stringlist_map(
+ struct sieve_ast_argument **listitem, void *context,
+ int (*map_function)(void *context, struct sieve_ast_argument *arg))
+{
+ if (sieve_ast_argument_type(*listitem) == SAAT_STRING) {
+ /* Single string */
+ return map_function(context, *listitem);
+ } else if (sieve_ast_argument_type(*listitem) == SAAT_STRING_LIST) {
+ int ret = 0;
+
+ /* String list */
+ *listitem = sieve_ast_strlist_first(*listitem);
+
+ while (*listitem != NULL) {
+ if ((ret = map_function(context, *listitem)) <= 0)
+ return ret;
+
+ *listitem = sieve_ast_strlist_next(*listitem);
+ }
+
+ return ret;
+ }
+
+ i_unreached();
+ return -1;
+}
+
+struct sieve_ast_argument *
+sieve_ast_stringlist_join(struct sieve_ast_argument *list,
+ struct sieve_ast_argument *items)
+{
+ enum sieve_ast_argument_type list_type, items_type;
+ struct sieve_ast_argument *newlist;
+
+ list_type = sieve_ast_argument_type(list);
+ items_type = sieve_ast_argument_type(items);
+
+ switch (list_type) {
+ case SAAT_STRING:
+ switch (items_type) {
+ case SAAT_STRING:
+ newlist = sieve_ast_argument_create(
+ list->ast, list->source_line);
+ newlist->type = SAAT_STRING_LIST;
+ newlist->_value.strlist = NULL;
+
+ sieve_ast_argument_substitute(list, newlist);
+ sieve_ast_arguments_detach(items, 1);
+
+ if (!_sieve_ast_stringlist_add_item(newlist, list) ||
+ !_sieve_ast_stringlist_add_item(newlist, items))
+ return NULL;
+ return newlist;
+ case SAAT_STRING_LIST:
+ /* Adding stringlist to string; make them swith places
+ and add one to the other.
+ */
+ sieve_ast_arguments_detach(items, 1);
+ sieve_ast_argument_substitute(list, items);
+ if (!_sieve_ast_stringlist_add_item(items, list))
+ return NULL;
+ return list;
+ default:
+ i_unreached();
+ }
+ break;
+ case SAAT_STRING_LIST:
+ switch (items_type) {
+ case SAAT_STRING:
+ /* Adding string to stringlist; straightforward add */
+ sieve_ast_arguments_detach(items, 1);
+ if (!_sieve_ast_stringlist_add_item(list, items))
+ return NULL;
+ return list;
+ case SAAT_STRING_LIST:
+ /* Adding stringlist to stringlist; perform actual join
+ */
+ sieve_ast_arguments_detach(items, 1);
+ if (!sieve_ast_stringlist_add_stringlist(list, items))
+ return NULL;
+ return list;
+ default:
+ i_unreached();
+ }
+ break;
+ default:
+ i_unreached();
+ }
+ return NULL;
+}
+
+/* Debug */
+
+/* Unparsing, currently implemented using plain printf()s */
+
+static void sieve_ast_unparse_string(const string_t *strval)
+{
+ char *str = t_strdup_noconst(str_c((string_t *)strval));
+
+ if (strchr(str, '\n') != NULL && str[strlen(str)-1] == '\n') {
+ /* Print it as a multi-line string and do required dotstuffing
+ */
+ char *spos = str;
+ char *epos = strchr(str, '\n');
+ printf("text:\n");
+
+ while (epos != NULL) {
+ *epos = '\0';
+ if (*spos == '.')
+ printf(".");
+
+ printf("%s\n", spos);
+
+ spos = epos+1;
+ epos = strchr(spos, '\n');
+ }
+ if (*spos == '.')
+ printf(".");
+
+ printf("%s\n.\n", spos);
+ } else {
+ /* Print it as a quoted string and escape " */
+ char *spos = str;
+ char *epos = strchr(str, '"');
+ printf("\"");
+
+ while (epos != NULL) {
+ *epos = '\0';
+ printf("%s\\\"", spos);
+
+ spos = epos+1;
+ epos = strchr(spos, '"');
+ }
+
+ printf("%s\"", spos);
+ }
+}
+
+static void
+sieve_ast_unparse_argument(struct sieve_ast_argument *argument, int level);
+
+static void
+sieve_ast_unparse_stringlist(struct sieve_ast_argument *strlist, int level)
+{
+ struct sieve_ast_argument *stritem;
+
+ if (sieve_ast_strlist_count(strlist) > 1) {
+ int i;
+
+ printf("[\n");
+
+ /* Create indent */
+ for (i = 0; i < level+2; i++)
+ printf(" ");
+
+ stritem = sieve_ast_strlist_first(strlist);
+ if (stritem != NULL) {
+ sieve_ast_unparse_string(
+ sieve_ast_strlist_str(stritem));
+
+ stritem = sieve_ast_strlist_next(stritem);
+ while (stritem != NULL) {
+ printf(",\n");
+ for (i = 0; i < level+2; i++)
+ printf(" ");
+ sieve_ast_unparse_string(
+ sieve_ast_strlist_str(stritem));
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+ }
+
+ printf(" ]");
+ } else {
+ stritem = sieve_ast_strlist_first(strlist);
+ if (stritem != NULL) {
+ sieve_ast_unparse_string(
+ sieve_ast_strlist_str(stritem));
+ }
+ }
+}
+
+static void
+sieve_ast_unparse_argument(struct sieve_ast_argument *argument, int level)
+{
+ switch (argument->type) {
+ case SAAT_STRING:
+ sieve_ast_unparse_string(sieve_ast_argument_str(argument));
+ break;
+ case SAAT_STRING_LIST:
+ sieve_ast_unparse_stringlist(argument, level+1);
+ break;
+ case SAAT_NUMBER:
+ printf("%"SIEVE_PRI_NUMBER,
+ sieve_ast_argument_number(argument));
+ break;
+ case SAAT_TAG:
+ printf(":%s", sieve_ast_argument_tag(argument));
+ break;
+ default:
+ printf("??ARGUMENT??");
+ break;
+ }
+}
+
+static void sieve_ast_unparse_test(struct sieve_ast_node *node, int level);
+
+static void sieve_ast_unparse_tests(struct sieve_ast_node *node, int level)
+{
+ struct sieve_ast_node *test;
+
+ if (sieve_ast_test_count(node) > 1) {
+ int i;
+
+ printf(" (\n");
+
+ /* Create indent */
+ for (i = 0; i < level+2; i++)
+ printf(" ");
+
+ test = sieve_ast_test_first(node);
+ sieve_ast_unparse_test(test, level+1);
+
+ test = sieve_ast_test_next(test);
+ while (test != NULL) {
+ printf(", \n");
+ for (i = 0; i < level+2; i++)
+ printf(" ");
+ sieve_ast_unparse_test(test, level+1);
+ test = sieve_ast_test_next(test);
+ }
+
+ printf(" )");
+ } else {
+ test = sieve_ast_test_first(node);
+ if (test != NULL)
+ sieve_ast_unparse_test(test, level);
+ }
+}
+
+static void sieve_ast_unparse_test(struct sieve_ast_node *node, int level)
+{
+ struct sieve_ast_argument *argument;
+
+ printf(" %s", node->identifier);
+
+ argument = sieve_ast_argument_first(node);
+ while (argument != NULL) {
+ printf(" ");
+ sieve_ast_unparse_argument(argument, level);
+ argument = sieve_ast_argument_next(argument);
+ }
+
+ sieve_ast_unparse_tests(node, level);
+}
+
+static void sieve_ast_unparse_command(struct sieve_ast_node *node, int level)
+{
+ struct sieve_ast_node *command;
+ struct sieve_ast_argument *argument;
+
+ int i;
+
+ /* Create indent */
+ for (i = 0; i < level; i++)
+ printf(" ");
+
+ printf("%s", node->identifier);
+
+ argument = sieve_ast_argument_first(node);
+ while (argument != NULL) {
+ printf(" ");
+ sieve_ast_unparse_argument(argument, level);
+ argument = sieve_ast_argument_next(argument);
+ }
+
+ sieve_ast_unparse_tests(node, level);
+
+ command = sieve_ast_command_first(node);
+ if (command != NULL) {
+ printf(" {\n");
+
+ while (command != NULL) {
+ sieve_ast_unparse_command(command, level+1);
+ command = sieve_ast_command_next(command);
+ }
+
+ for (i = 0; i < level; i++)
+ printf(" ");
+ printf("}\n");
+ } else
+ printf(";\n");
+}
+
+void sieve_ast_unparse(struct sieve_ast *ast)
+{
+ struct sieve_ast_node *command;
+
+ printf("Unparsing Abstract Syntax Tree:\n");
+
+ T_BEGIN {
+ command = sieve_ast_command_first(sieve_ast_root(ast));
+ while (command != NULL) {
+ sieve_ast_unparse_command(command, 0);
+ command = sieve_ast_command_next(command);
+ }
+ } T_END;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-ast.h b/pigeonhole/src/lib-sieve/sieve-ast.h
new file mode 100644
index 0000000..00f8a6b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-ast.h
@@ -0,0 +1,382 @@
+#ifndef SIEVE_AST_H
+#define SIEVE_AST_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+/*
+ Abstract Syntax Tree (AST) structure:
+
+ sieve_ast (root)
+ [*command]
+ |
+ +-- command:
+ | ....
+ +-- command:
+ | [identifier *argument *test *command]
+ | +-- argument: | \--> as from root
+ | | .... |
+ | +-- argument: V (continued below)
+ | | [number | tag | *string]
+ | .
+ .
+
+ *test
+ +-- test:
+ | ....
+ +-- test:
+ | [identifier *argument *test]
+ | +-- argument: \--> as from the top
+ . | .... of this tree
+ +-- argument:
+ | [number | tag | *string]
+ .
+
+ Tests and commands are defined using the same structure: sieve_ast_node.
+ However, arguments and string-lists are described using sieve_ast_argument.
+*/
+
+/* IMPORTANT NOTICE: Do not decorate the AST with objects other than those
+ allocated on the ast's pool or static const objects. Otherwise it is possible
+ that pointers in the tree become dangling which is highly undesirable.
+ */
+
+/*
+ * Forward declarations
+ */
+
+struct sieve_ast_list;
+struct sieve_ast_arg_list;
+
+/*
+ * Types
+ */
+
+enum sieve_ast_argument_type {
+ SAAT_NONE,
+ SAAT_NUMBER,
+ SAAT_STRING,
+ SAAT_STRING_LIST,
+ SAAT_TAG,
+};
+
+enum sieve_ast_type {
+ SAT_NONE,
+ SAT_ROOT,
+ SAT_COMMAND,
+ SAT_TEST,
+};
+
+/*
+ * AST Nodes
+ */
+
+/* Argument node */
+
+struct sieve_ast_argument {
+ enum sieve_ast_argument_type type;
+
+ /* Back reference to the AST object */
+ struct sieve_ast *ast;
+
+ /* List related */
+ struct sieve_ast_arg_list *list;
+ struct sieve_ast_argument *next;
+ struct sieve_ast_argument *prev;
+
+ /* Parser-assigned data */
+
+ union {
+ string_t *str;
+ struct sieve_ast_arg_list *strlist;
+ const char *tag;
+ sieve_number_t number;
+ } _value;
+
+ unsigned int source_line;
+
+ /* Assigned during validation */
+
+ /* Argument associated with this ast element */
+ struct sieve_argument *argument;
+
+ /* Parameters to this (tag) argument */
+ struct sieve_ast_argument *parameters;
+};
+
+struct sieve_ast_node {
+ enum sieve_ast_type type;
+
+ /* Back reference to the AST object */
+ struct sieve_ast *ast;
+
+ /* Back reference to this node's parent */
+ struct sieve_ast_node *parent;
+
+ /* Linked list references */
+ struct sieve_ast_list *list;
+ struct sieve_ast_node *next;
+ struct sieve_ast_node *prev;
+
+ /* Commands (NULL if not allocated) */
+ bool block;
+ struct sieve_ast_list *commands;
+
+ /* Tests (NULL if not allocated)*/
+ bool test_list;
+ struct sieve_ast_list *tests;
+
+ /* Arguments (NULL if not allocated) */
+ struct sieve_ast_arg_list *arguments;
+
+ /* Identifier of command or test */
+ const char *identifier;
+
+ /* The location in the file where this command was started */
+ unsigned int source_line;
+
+ /* Assigned during validation */
+
+ /* Context */
+ struct sieve_command *command;
+};
+
+/*
+ * AST node lists
+ */
+
+struct sieve_ast_list {
+ struct sieve_ast_node *head;
+ struct sieve_ast_node *tail;
+ unsigned int len;
+};
+
+struct sieve_ast_arg_list {
+ struct sieve_ast_argument *head;
+ struct sieve_ast_argument *tail;
+ unsigned int len;
+};
+
+/*
+ * AST object
+ */
+
+struct sieve_ast;
+
+struct sieve_ast *sieve_ast_create(struct sieve_script *script);
+void sieve_ast_ref(struct sieve_ast *ast);
+void sieve_ast_unref(struct sieve_ast **ast);
+
+struct sieve_ast_node *sieve_ast_root(struct sieve_ast *ast);
+pool_t sieve_ast_pool(struct sieve_ast *ast);
+struct sieve_script *sieve_ast_script(struct sieve_ast *ast);
+
+/* Extension support */
+
+struct sieve_ast_extension {
+ const struct sieve_extension_def *ext;
+
+ void (*free)(const struct sieve_extension *ext, struct sieve_ast *ast,
+ void *context);
+};
+
+void sieve_ast_extension_link(struct sieve_ast *ast,
+ const struct sieve_extension *ext,
+ bool required);
+const struct sieve_extension * const *
+sieve_ast_extensions_get(struct sieve_ast *ast, unsigned int *count_r);
+
+void sieve_ast_extension_register(struct sieve_ast *ast,
+ const struct sieve_extension *ext,
+ const struct sieve_ast_extension *ast_ext,
+ void *context);
+void sieve_ast_extension_set_context(struct sieve_ast *ast,
+ const struct sieve_extension *ext,
+ void *context);
+void *sieve_ast_extension_get_context(struct sieve_ast *ast,
+ const struct sieve_extension *ext);
+
+bool sieve_ast_extension_is_required(struct sieve_ast *ast,
+ const struct sieve_extension *ext);
+
+/*
+ * AST node manipulation
+ */
+
+/* Command nodes */
+
+struct sieve_ast_node *
+sieve_ast_test_create(struct sieve_ast_node *parent, const char *identifier,
+ unsigned int source_line);
+struct sieve_ast_node *
+sieve_ast_command_create(struct sieve_ast_node *parent, const char *identifier,
+ unsigned int source_line);
+
+struct sieve_ast_node *
+sieve_ast_node_detach(struct sieve_ast_node *first);
+
+const char *sieve_ast_type_name(enum sieve_ast_type ast_type);
+
+/* Argument nodes */
+
+struct sieve_ast_argument *
+sieve_ast_argument_create(struct sieve_ast *ast, unsigned int source_line);
+
+struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool);
+bool sieve_ast_arg_list_add(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *argument);
+bool sieve_ast_arg_list_insert(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *before,
+ struct sieve_ast_argument *argument);
+void sieve_ast_arg_list_substitute(struct sieve_ast_arg_list *list,
+ struct sieve_ast_argument *argument,
+ struct sieve_ast_argument *replacement);
+
+struct sieve_ast_argument *
+sieve_ast_argument_string_create_raw(struct sieve_ast *ast, string_t *str,
+ unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_string_create(struct sieve_ast_node *node,
+ const string_t *str, unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_cstring_create(struct sieve_ast_node *node, const char *str,
+ unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_create(struct sieve_ast_node *node, const char *tag,
+ unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_number_create(struct sieve_ast_node *node,
+ sieve_number_t number,
+ unsigned int source_line);
+
+void sieve_ast_argument_string_set(struct sieve_ast_argument *argument,
+ string_t *newstr);
+void sieve_ast_argument_string_setc(struct sieve_ast_argument *argument,
+ const char *newstr);
+
+void sieve_ast_argument_number_set(struct sieve_ast_argument *argument,
+ sieve_number_t newnum);
+void sieve_ast_argument_number_substitute(struct sieve_ast_argument *argument,
+ sieve_number_t number);
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_insert(struct sieve_ast_argument *before,
+ const char *tag, unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_create(struct sieve_ast_node *node,
+ unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_substitute(struct sieve_ast_node *node,
+ struct sieve_ast_argument *arg);
+
+struct sieve_ast_argument *
+sieve_ast_arguments_detach(struct sieve_ast_argument *first,
+ unsigned int count);
+bool sieve_ast_argument_attach(struct sieve_ast_node *node,
+ struct sieve_ast_argument *argument);
+
+const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type);
+#define sieve_ast_argument_name(argument) \
+ sieve_ast_argument_type_name((argument)->type)
+
+bool sieve_ast_stringlist_add(struct sieve_ast_argument *list,
+ const string_t *str, unsigned int source_line);
+bool sieve_ast_stringlist_add_strc(struct sieve_ast_argument *list,
+ const char *str, unsigned int source_line);
+
+/*
+ * Utility
+ */
+
+int sieve_ast_stringlist_map(
+ struct sieve_ast_argument **listitem, void *context,
+ int (*map_function)(void *context, struct sieve_ast_argument *arg));
+struct sieve_ast_argument *
+sieve_ast_stringlist_join(struct sieve_ast_argument *list,
+ struct sieve_ast_argument *items);
+
+/*
+ * AST access macros
+ */
+
+/* Generic list access macros */
+#define __AST_LIST_FIRST(list) \
+ ((list) == NULL ? NULL : (list)->head)
+#define __AST_LIST_LAST(list) \
+ ((list) == NULL ? NULL : (list)->tail)
+#define __AST_LIST_COUNT(list) \
+ ((list) == NULL || (list)->head == NULL ? 0 : (list)->len)
+#define __AST_LIST_NEXT(item) ((item)->next)
+#define __AST_LIST_PREV(item) ((item)->prev)
+
+#define __AST_NODE_LIST_FIRST(node, list) __AST_LIST_FIRST((node)->list)
+#define __AST_NODE_LIST_LAST(node, list) __AST_LIST_LAST((node)->list)
+#define __AST_NODE_LIST_COUNT(node, list) __AST_LIST_COUNT((node)->list)
+
+/* AST macros */
+
+/* AST node macros */
+#define sieve_ast_node_pool(node) (sieve_ast_pool((node)->ast))
+#define sieve_ast_node_parent(node) ((node)->parent)
+#define sieve_ast_node_prev(node) __AST_LIST_PREV(node)
+#define sieve_ast_node_next(node) __AST_LIST_NEXT(node)
+#define sieve_ast_node_type(node) ((node) == NULL ? SAT_NONE : (node)->type)
+#define sieve_ast_node_line(node) ((node) == NULL ? 0 : (node)->source_line)
+
+/* AST command node macros */
+#define sieve_ast_command_first(node) __AST_NODE_LIST_FIRST(node, commands)
+#define sieve_ast_command_count(node) __AST_NODE_LIST_COUNT(node, commands)
+#define sieve_ast_command_prev(command) __AST_LIST_PREV(command)
+#define sieve_ast_command_next(command) __AST_LIST_NEXT(command)
+
+/* Compare the identifier of the previous command */
+#define sieve_ast_prev_cmd_is(cmd, id) \
+ ((cmd)->prev == NULL ? FALSE : \
+ strncasecmp((cmd)->prev->identifier, id, sizeof(id)-1) == 0)
+
+/* AST test macros */
+#define sieve_ast_test_count(node) __AST_NODE_LIST_COUNT(node, tests)
+#define sieve_ast_test_first(node) __AST_NODE_LIST_FIRST(node, tests)
+#define sieve_ast_test_next(test) __AST_LIST_NEXT(test)
+
+/* AST argument macros */
+#define sieve_ast_argument_pool(node) (sieve_ast_pool((node)->ast))
+#define sieve_ast_argument_first(node) __AST_NODE_LIST_FIRST(node, arguments)
+#define sieve_ast_argument_last(node) __AST_NODE_LIST_LAST(node, arguments)
+#define sieve_ast_argument_count(node) __AST_NODE_LIST_COUNT(node, arguments)
+#define sieve_ast_argument_prev(argument) __AST_LIST_PREV(argument)
+#define sieve_ast_argument_next(argument) __AST_LIST_NEXT(argument)
+#define sieve_ast_argument_type(argument) (argument)->type
+#define sieve_ast_argument_line(argument) (argument)->source_line
+
+#define sieve_ast_argument_str(argument) ((argument)->_value.str)
+#define sieve_ast_argument_strc(argument) (str_c((argument)->_value.str))
+#define sieve_ast_argument_tag(argument) ((argument)->_value.tag)
+#define sieve_ast_argument_number(argument) ((argument)->_value.number)
+
+/* AST string list macros */
+// @UNSAFE: should check whether we are actually accessing a string list
+#define sieve_ast_strlist_first(list) \
+ __AST_NODE_LIST_FIRST(list, _value.strlist)
+#define sieve_ast_strlist_last(list) \
+ __AST_NODE_LIST_LAST(list, _value.strlist)
+#define sieve_ast_strlist_count(list) \
+ __AST_NODE_LIST_COUNT(list, _value.strlist)
+#define sieve_ast_strlist_next(str) __AST_LIST_NEXT(str)
+#define sieve_ast_strlist_prev(str) __AST_LIST_PREV(str)
+#define sieve_ast_strlist_str(str) sieve_ast_argument_str(str)
+#define sieve_ast_strlist_strc(str) sieve_ast_argument_strc(str)
+
+/*
+ * Debug
+ */
+
+void sieve_ast_unparse(struct sieve_ast *ast);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-code.c b/pigeonhole/src/lib-sieve/sieve-binary-code.c
new file mode 100644
index 0000000..0d76ee0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-code.c
@@ -0,0 +1,405 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Forward declarations
+ */
+
+static inline sieve_size_t
+sieve_binary_emit_dynamic_data(struct sieve_binary_block *sblock,
+ const void *data, size_t size);
+
+/*
+ * Emission functions
+ */
+
+/* Low-level emission functions */
+
+static inline void
+_sieve_binary_emit_data(struct sieve_binary_block *sblock,
+ const void *data, sieve_size_t size)
+{
+ buffer_append(sblock->data, data, size);
+}
+
+static inline void
+_sieve_binary_emit_byte(struct sieve_binary_block *sblock, uint8_t byte)
+{
+ _sieve_binary_emit_data(sblock, &byte, 1);
+}
+
+static inline void
+_sieve_binary_update_data(struct sieve_binary_block *sblock,
+ sieve_size_t address, const void *data,
+ sieve_size_t size)
+{
+ buffer_write(sblock->data, address, data, size);
+}
+
+sieve_size_t sieve_binary_emit_data(struct sieve_binary_block *sblock,
+ const void *data, sieve_size_t size)
+{
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+
+ _sieve_binary_emit_data(sblock, data, size);
+
+ return address;
+}
+
+sieve_size_t sieve_binary_emit_byte(struct sieve_binary_block *sblock,
+ uint8_t byte)
+{
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+
+ _sieve_binary_emit_data(sblock, &byte, 1);
+
+ return address;
+}
+
+void sieve_binary_update_data(struct sieve_binary_block *sblock,
+ sieve_size_t address, const void *data,
+ sieve_size_t size)
+{
+ _sieve_binary_update_data(sblock, address, data, size);
+}
+
+/* Offset emission functions */
+
+sieve_size_t sieve_binary_emit_offset(struct sieve_binary_block *sblock,
+ sieve_offset_t offset)
+{
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+ uint8_t encoded[sizeof(offset)];
+ int i;
+
+ for (i = sizeof(offset)-1; i >= 0; i--) {
+ encoded[i] = (uint8_t)offset;
+ offset >>= 8;
+ }
+
+ _sieve_binary_emit_data(sblock, encoded, sizeof(offset));
+
+ return address;
+}
+
+void sieve_binary_resolve_offset(struct sieve_binary_block *sblock,
+ sieve_size_t address)
+{
+ sieve_size_t cur_address = _sieve_binary_block_get_size(sblock);
+ sieve_offset_t offset;
+ uint8_t encoded[sizeof(offset)];
+ int i;
+
+ i_assert(cur_address > address);
+ i_assert((cur_address - address) <= (sieve_offset_t)-1);
+ offset = cur_address - address;
+ for (i = sizeof(offset)-1; i >= 0; i--) {
+ encoded[i] = (uint8_t)offset;
+ offset >>= 8;
+ }
+
+ _sieve_binary_update_data(sblock, address, encoded, sizeof(offset));
+}
+
+/* Literal emission */
+
+sieve_size_t sieve_binary_emit_integer(struct sieve_binary_block *sblock,
+ sieve_number_t integer)
+{
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+ uint8_t buffer[sizeof(sieve_number_t) + 1];
+ int bufpos = sizeof(buffer) - 1;
+
+ /* Encode last byte [0xxxxxxx]; msb == 0 marks the last byte */
+ buffer[bufpos] = integer & 0x7F;
+ bufpos--;
+
+ /* Encode first bytes [1xxxxxxx] */
+ integer >>= 7;
+ while (integer > 0) {
+ buffer[bufpos] = (integer & 0x7F) | 0x80;
+ bufpos--;
+ integer >>= 7;
+ }
+
+ /* Emit encoded integer */
+ bufpos++;
+ _sieve_binary_emit_data(sblock, buffer + bufpos, sizeof(buffer) - bufpos);
+
+ return address;
+}
+
+static inline sieve_size_t
+sieve_binary_emit_dynamic_data(struct sieve_binary_block *sblock,
+ const void *data, sieve_size_t size)
+{
+ sieve_size_t address =
+ sieve_binary_emit_integer(sblock, (sieve_number_t)size);
+
+ _sieve_binary_emit_data(sblock, data, size);
+
+ return address;
+}
+
+sieve_size_t sieve_binary_emit_cstring(struct sieve_binary_block *sblock,
+ const char *str)
+{
+ sieve_size_t address =
+ sieve_binary_emit_dynamic_data(sblock, (void *)str,
+ (sieve_size_t)strlen(str));
+
+ _sieve_binary_emit_byte(sblock, 0);
+ return address;
+}
+
+sieve_size_t sieve_binary_emit_string(struct sieve_binary_block *sblock,
+ const string_t *str)
+{
+ sieve_size_t address =
+ sieve_binary_emit_dynamic_data(sblock, (void *)str_data(str),
+ (sieve_size_t)str_len(str));
+
+ _sieve_binary_emit_byte(sblock, 0);
+ return address;
+}
+
+/*
+ * Extension emission
+ */
+
+sieve_size_t sieve_binary_emit_extension(struct sieve_binary_block *sblock,
+ const struct sieve_extension *ext,
+ unsigned int offset)
+{
+ sieve_size_t address = _sieve_binary_block_get_size(sblock);
+ struct sieve_binary_extension_reg *ereg = NULL;
+
+ (void)sieve_binary_extension_register(sblock->sbin, ext, &ereg);
+
+ i_assert(ereg != NULL);
+
+ _sieve_binary_emit_byte(sblock, offset + ereg->index);
+ return address;
+}
+
+void sieve_binary_emit_extension_object(
+ struct sieve_binary_block *sblock,
+ const struct sieve_extension_objects *objs, unsigned int code)
+{
+ if (objs->count > 1)
+ _sieve_binary_emit_byte(sblock, code);
+}
+
+/*
+ * Code retrieval
+ */
+
+#define ADDR_CODE_READ(block) \
+ size_t _code_size; \
+ const int8_t *_code = buffer_get_data((block)->data, &_code_size)
+
+#define ADDR_CODE_AT(address) \
+ ((int8_t)(_code[*address]))
+#define ADDR_DATA_AT(address) \
+ ((uint8_t)(_code[*address]))
+#define ADDR_POINTER(address) \
+ ((const int8_t *)(&_code[*address]))
+
+#define ADDR_BYTES_LEFT(address) \
+ ((*address) > _code_size ? 0 : ((_code_size) - (*address)))
+#define ADDR_JUMP(address, offset) \
+ (*address) += offset
+
+/* Literals */
+
+bool sieve_binary_read_byte(struct sieve_binary_block *sblock,
+ sieve_size_t *address, unsigned int *byte_r)
+{
+ ADDR_CODE_READ(sblock);
+
+ if (ADDR_BYTES_LEFT(address) >= 1) {
+ if (byte_r != NULL)
+ *byte_r = ADDR_DATA_AT(address);
+ ADDR_JUMP(address, 1);
+
+ return TRUE;
+ }
+
+ if (byte_r != NULL)
+ *byte_r = 0;
+ return FALSE;
+}
+
+bool sieve_binary_read_code(struct sieve_binary_block *sblock,
+ sieve_size_t *address, signed int *code_r)
+{
+ ADDR_CODE_READ(sblock);
+
+ if (ADDR_BYTES_LEFT(address) >= 1) {
+ if (code_r != NULL)
+ *code_r = ADDR_CODE_AT(address);
+ ADDR_JUMP(address, 1);
+
+ return TRUE;
+ }
+
+ if (code_r != NULL)
+ *code_r = 0;
+ return FALSE;
+}
+
+
+bool sieve_binary_read_offset(struct sieve_binary_block *sblock,
+ sieve_size_t *address, sieve_offset_t *offset_r)
+{
+ sieve_offset_t offs = 0;
+ ADDR_CODE_READ(sblock);
+
+ if (ADDR_BYTES_LEFT(address) >= 4) {
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ offs = (offs << 8) + ADDR_DATA_AT(address);
+ ADDR_JUMP(address, 1);
+ }
+
+ if (offset_r != NULL)
+ *offset_r = offs;
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* FIXME: might need negative numbers in the future */
+bool sieve_binary_read_integer(struct sieve_binary_block *sblock,
+ sieve_size_t *address, sieve_number_t *int_r)
+{
+ int bits = sizeof(sieve_number_t) * 8;
+ sieve_number_t integer = 0;
+
+ ADDR_CODE_READ(sblock);
+
+ if (ADDR_BYTES_LEFT(address) == 0)
+ return FALSE;
+
+ /* Read first integer bytes [1xxxxxxx] */
+ while ((ADDR_DATA_AT(address) & 0x80) > 0) {
+ if (ADDR_BYTES_LEFT(address) > 0 && bits > 0) {
+ integer |= ADDR_DATA_AT(address) & 0x7F;
+ ADDR_JUMP(address, 1);
+
+ /* Each byte encodes 7 bits of the integer */
+ integer <<= 7;
+ bits -= 7;
+ } else {
+ /* This is an error */
+ return FALSE;
+ }
+ }
+
+ /* Read last byte [0xxxxxxx] */
+ integer |= ADDR_DATA_AT(address) & 0x7F;
+ ADDR_JUMP(address, 1);
+
+ if (int_r != NULL)
+ *int_r = integer;
+ return TRUE;
+}
+
+bool sieve_binary_read_string(struct sieve_binary_block *sblock,
+ sieve_size_t *address, string_t **str_r)
+{
+ unsigned int strlen = 0;
+ const char *strdata;
+
+ ADDR_CODE_READ(sblock);
+
+ if (!sieve_binary_read_unsigned(sblock, address, &strlen))
+ return FALSE;
+
+ if (strlen > ADDR_BYTES_LEFT(address))
+ return FALSE;
+
+ strdata = (const char *)ADDR_POINTER(address);
+ ADDR_JUMP(address, strlen);
+
+ if (ADDR_CODE_AT(address) != 0)
+ return FALSE;
+
+ if (str_r != NULL)
+ *str_r = t_str_new_const(strdata, strlen);
+
+ ADDR_JUMP(address, 1);
+
+ return TRUE;
+}
+
+bool sieve_binary_read_extension(struct sieve_binary_block *sblock,
+ sieve_size_t *address, unsigned int *offset_r,
+ const struct sieve_extension **ext_r)
+{
+ unsigned int code;
+ unsigned int offset = *offset_r;
+ const struct sieve_extension *ext = NULL;
+
+ ADDR_CODE_READ(sblock);
+
+ if (ADDR_BYTES_LEFT(address) == 0)
+ return FALSE;
+
+ *offset_r = code = ADDR_DATA_AT(address);
+ ADDR_JUMP(address, 1);
+
+ if (code >= offset) {
+ ext = sieve_binary_extension_get_by_index(sblock->sbin,
+ (code - offset));
+ if (ext == NULL)
+ return FALSE;
+ }
+
+ if (ext_r != NULL)
+ *ext_r = ext;
+ return TRUE;
+}
+
+const void *
+sieve_binary_read_extension_object(struct sieve_binary_block *sblock,
+ sieve_size_t *address,
+ const struct sieve_extension_objects *objs)
+{
+ unsigned int code;
+
+ ADDR_CODE_READ(sblock);
+
+ if (objs->count == 0)
+ return NULL;
+ if (objs->count == 1)
+ return objs->objects;
+ if (ADDR_BYTES_LEFT(address) == 0)
+ return NULL;
+
+ code = ADDR_DATA_AT(address);
+ ADDR_JUMP(address, 1);
+
+ if (code >= objs->count)
+ return NULL;
+ return ((const void *const *)objs->objects)[code];
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-debug.c b/pigeonhole/src/lib-sieve/sieve-binary-debug.c
new file mode 100644
index 0000000..5e8b13a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-debug.c
@@ -0,0 +1,276 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-code.h"
+
+#include "sieve-binary-private.h"
+
+/* Quick 'n dirty debug */
+#if 0
+#define debug_printf(...) printf ("lineinfo: " __VA_ARGS__)
+#else
+#define debug_printf(...)
+#endif
+
+/*
+ * Opcodes
+ */
+
+enum {
+ LINPROG_OP_COPY,
+ LINPROG_OP_ADVANCE_PC,
+ LINPROG_OP_ADVANCE_LINE,
+ LINPROG_OP_SET_COLUMN,
+ LINPROG_OP_SPECIAL_BASE
+};
+
+#define LINPROG_LINE_BASE 0
+#define LINPROG_LINE_RANGE 4
+
+/*
+ * Lineinfo writer
+ */
+
+struct sieve_binary_debug_writer {
+ struct sieve_binary_block *sblock;
+
+ sieve_size_t address;
+ unsigned int line;
+ unsigned int column;
+};
+
+struct sieve_binary_debug_writer *
+sieve_binary_debug_writer_init(struct sieve_binary_block *sblock)
+{
+ struct sieve_binary_debug_writer *dwriter;
+
+ dwriter = i_new(struct sieve_binary_debug_writer, 1);
+ dwriter->sblock = sblock;
+
+ return dwriter;
+}
+
+void sieve_binary_debug_writer_deinit(
+ struct sieve_binary_debug_writer **dwriter)
+{
+ i_free(*dwriter);
+ *dwriter = NULL;
+}
+
+void sieve_binary_debug_emit(struct sieve_binary_debug_writer *dwriter,
+ sieve_size_t code_address, unsigned int code_line,
+ unsigned int code_column)
+{
+ i_assert(code_address >= dwriter->address);
+
+ struct sieve_binary_block *sblock = dwriter->sblock;
+ sieve_size_t address_inc = code_address - dwriter->address;
+ int line_inc = (code_line > dwriter->line ?
+ (int)(code_line - dwriter->line) :
+ -(int)(dwriter->line - code_line));
+ unsigned int sp_opcode = 0;
+
+ /* Check for applicability of special opcode */
+ if (line_inc > 0 &&
+ (LINPROG_LINE_BASE + LINPROG_LINE_RANGE - 1) >= line_inc) {
+ sp_opcode = LINPROG_OP_SPECIAL_BASE +
+ (line_inc - LINPROG_LINE_BASE) +
+ (LINPROG_LINE_RANGE * address_inc);
+
+ if (sp_opcode > 255)
+ sp_opcode = 0;
+ }
+
+ /* Update line and address */
+ if (sp_opcode == 0) {
+ if (line_inc != 0) {
+ (void)sieve_binary_emit_byte(sblock,
+ LINPROG_OP_ADVANCE_LINE);
+ (void)sieve_binary_emit_unsigned(
+ sblock, (unsigned int)line_inc);
+ }
+
+ if (address_inc > 0) {
+ (void)sieve_binary_emit_byte(sblock,
+ LINPROG_OP_ADVANCE_PC);
+ (void)sieve_binary_emit_unsigned(sblock, address_inc);
+ }
+ } else {
+ (void)sieve_binary_emit_byte(sblock, sp_opcode);
+ }
+
+ /* Set column */
+ if (dwriter->column != code_column) {
+ (void)sieve_binary_emit_byte(sblock, LINPROG_OP_SET_COLUMN);
+ (void)sieve_binary_emit_unsigned(sblock, code_column);
+ }
+
+ /* Generate matrix row */
+ (void)sieve_binary_emit_byte(sblock, LINPROG_OP_COPY);
+
+ dwriter->address = code_address;
+ dwriter->line = code_line;
+ dwriter->column = code_column;
+}
+
+/*
+ * Debug reader
+ */
+
+struct sieve_binary_debug_reader {
+ struct sieve_binary_block *sblock;
+
+ sieve_size_t address, last_address;
+ unsigned int line, last_line;
+
+ unsigned int column;
+
+ sieve_size_t state;
+};
+
+struct sieve_binary_debug_reader *
+sieve_binary_debug_reader_init(struct sieve_binary_block *sblock)
+{
+ struct sieve_binary_debug_reader *dreader;
+
+ dreader = i_new(struct sieve_binary_debug_reader, 1);
+ dreader->sblock = sblock;
+
+ return dreader;
+}
+
+void sieve_binary_debug_reader_deinit(
+ struct sieve_binary_debug_reader **dreader)
+{
+ i_free(*dreader);
+ *dreader = NULL;
+}
+
+void sieve_binary_debug_reader_reset(struct sieve_binary_debug_reader *dreader)
+{
+ dreader->address = 0;
+ dreader->line = 0;
+ dreader->column = 0;
+ dreader->state = 0;
+}
+
+unsigned int
+sieve_binary_debug_read_line(struct sieve_binary_debug_reader *dreader,
+ sieve_size_t code_address)
+{
+ size_t linprog_size;
+ sieve_size_t address;
+ unsigned int line;
+
+ if (code_address < dreader->last_address)
+ sieve_binary_debug_reader_reset(dreader);
+
+ if (code_address >= dreader->last_address &&
+ code_address < dreader->address) {
+ debug_printf("%08llx: NOOP [%08llx]\n",
+ (unsigned long long)dreader->state,
+ (unsigned long long)code_address);
+ return dreader->last_line;
+ }
+
+ address = dreader->address;
+ line = dreader->line;
+
+ debug_printf("%08llx: READ [%08llx]\n",
+ (unsigned long long)dreader->state,
+ (unsigned long long)code_address);
+
+ linprog_size = sieve_binary_block_get_size(dreader->sblock);
+ while (dreader->state < linprog_size) {
+ unsigned int opcode;
+ unsigned int value;
+ int line_inc;
+
+ if (sieve_binary_read_byte(dreader->sblock,
+ &dreader->state, &opcode)) {
+ switch (opcode) {
+ case LINPROG_OP_COPY:
+ debug_printf("%08llx: COPY ==> %08llx: %ld\n",
+ (unsigned long long)dreader->state,
+ (unsigned long long)address, line);
+
+ dreader->last_address = dreader->address;
+ dreader->last_line = dreader->line;
+
+ dreader->address = address;
+ dreader->line = line;
+
+ if (code_address < address)
+ return dreader->last_line;
+ else if (code_address == address)
+ return dreader->line;
+ break;
+ case LINPROG_OP_ADVANCE_PC:
+ debug_printf("%08llx: ADV_PC\n",
+ (unsigned long long)dreader->state);
+ if (!sieve_binary_read_unsigned(
+ dreader->sblock, &dreader->state,
+ &value)) {
+ sieve_binary_debug_reader_reset(dreader);
+ return 0;
+ }
+ debug_printf(" : + %d\n", value);
+ address += value;
+ break;
+ case LINPROG_OP_ADVANCE_LINE:
+ debug_printf("%08llx: ADV_LINE\n",
+ (unsigned long long)dreader->state);
+ if (!sieve_binary_read_unsigned(
+ dreader->sblock, &dreader->state,
+ &value)) {
+ sieve_binary_debug_reader_reset(dreader);
+ return 0;
+ }
+ line_inc = (int)value;
+ debug_printf(" : + %d\n", line_inc);
+ line = (line_inc > 0 ?
+ line + (unsigned int)line_inc :
+ line - (unsigned int)-line_inc);
+ break;
+ case LINPROG_OP_SET_COLUMN:
+ debug_printf("%08llx: SET_COL\n",
+ (unsigned long long)dreader->state);
+ if (!sieve_binary_read_unsigned(
+ dreader->sblock, &dreader->state,
+ &value)) {
+ sieve_binary_debug_reader_reset(dreader);
+ return 0;
+ }
+ debug_printf(" : = %d\n", value);
+ dreader->column = value;
+ break;
+ default:
+ opcode -= LINPROG_OP_SPECIAL_BASE;
+
+ address += (opcode / LINPROG_LINE_RANGE);
+ line += LINPROG_LINE_BASE +
+ (opcode % LINPROG_LINE_RANGE);
+
+ debug_printf("%08llx: SPECIAL\n",
+ (unsigned long long)dreader->state);
+ debug_printf(" : +A %d +L %d\n",
+ (opcode / LINPROG_LINE_RANGE),
+ LINPROG_LINE_BASE +
+ (opcode % LINPROG_LINE_RANGE));
+ break;
+ }
+ } else {
+ debug_printf("OPCODE READ FAILED\n");
+ sieve_binary_debug_reader_reset(dreader);
+ return 0;
+ }
+ }
+
+ return dreader->line;
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-dumper.c b/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
new file mode 100644
index 0000000..3b3bb4b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-dumper.c
@@ -0,0 +1,326 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+#include "array.h"
+#include "buffer.h"
+#include "time-util.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-dump.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Binary dumper object
+ */
+
+struct sieve_binary_dumper {
+ pool_t pool;
+
+ /* Dumptime environment */
+ struct sieve_dumptime_env dumpenv;
+};
+
+struct sieve_binary_dumper *
+sieve_binary_dumper_create(struct sieve_binary *sbin)
+{
+ pool_t pool;
+ struct sieve_binary_dumper *dumper;
+
+ pool = pool_alloconly_create("sieve_binary_dumper", 4096);
+ dumper = p_new(pool, struct sieve_binary_dumper, 1);
+ dumper->pool = pool;
+ dumper->dumpenv.dumper = dumper;
+
+ dumper->dumpenv.sbin = sbin;
+ sieve_binary_ref(sbin);
+
+ dumper->dumpenv.svinst = sieve_binary_svinst(sbin);
+
+ return dumper;
+}
+
+void sieve_binary_dumper_free(struct sieve_binary_dumper **dumper)
+{
+ sieve_binary_unref(&(*dumper)->dumpenv.sbin);
+ pool_unref(&((*dumper)->pool));
+
+ *dumper = NULL;
+}
+
+pool_t sieve_binary_dumper_pool(struct sieve_binary_dumper *dumper)
+{
+ return dumper->pool;
+}
+
+/*
+ * Formatted output
+ */
+
+void sieve_binary_dumpf(const struct sieve_dumptime_env *denv,
+ const char *fmt, ...)
+{
+ string_t *outbuf = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_vprintfa(outbuf, fmt, args);
+ va_end(args);
+
+ o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_binary_dump_sectionf(const struct sieve_dumptime_env *denv,
+ const char *fmt, ...)
+{
+ string_t *outbuf = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_printfa(outbuf, "\n* ");
+ str_vprintfa(outbuf, fmt, args);
+ str_printfa(outbuf, ":\n\n");
+ va_end(args);
+
+ o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+/*
+ * Dumping the binary
+ */
+
+bool sieve_binary_dumper_run(struct sieve_binary_dumper *dumper,
+ struct ostream *stream, bool verbose)
+{
+ struct sieve_binary *sbin = dumper->dumpenv.sbin;
+ struct sieve_script *script = sieve_binary_script(sbin);
+ struct sieve_dumptime_env *denv = &(dumper->dumpenv);
+ const struct sieve_binary_header *header = &sbin->header;
+ struct sieve_binary_block *sblock;
+ bool success = TRUE;
+ sieve_size_t offset;
+ int count, i;
+
+ dumper->dumpenv.stream = stream;
+
+ /* Dump header */
+
+ sieve_binary_dump_sectionf(denv, "Header");
+
+ sieve_binary_dumpf(denv,
+ "version = %"PRIu16".%"PRIu16"\n"
+ "flags = 0x%08"PRIx32"\n",
+ header->version_major, header->version_minor,
+ header->flags);
+ if (header->resource_usage.update_time != 0) {
+ time_t update_time =
+ (time_t)header->resource_usage.update_time;
+ sieve_binary_dumpf(denv,
+ "resource usage:\n"
+ " update time = %s\n"
+ " cpu time = %"PRIu32" ms\n",
+ t_strflocaltime("%Y-%m-%d %H:%M:%S",
+ update_time),
+ header->resource_usage.cpu_time_msecs);
+ }
+
+ /* Dump list of binary blocks */
+
+ if (verbose) {
+ count = sieve_binary_block_count(sbin);
+
+ sieve_binary_dump_sectionf(denv, "Binary blocks (count: %d)",
+ count);
+
+ for (i = 0; i < count; i++) {
+ struct sieve_binary_block *sblock =
+ sieve_binary_block_get(sbin, i);
+
+ sieve_binary_dumpf(
+ denv, "%3d: size: %zu bytes\n",
+ i, sieve_binary_block_get_size(sblock));
+ }
+ }
+
+ /* Dump script metadata */
+
+ sieve_binary_dump_sectionf(denv, "Script metadata (block: %d)",
+ SBIN_SYSBLOCK_SCRIPT_DATA);
+ sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_SCRIPT_DATA);
+
+ T_BEGIN {
+ offset = 0;
+ success = sieve_script_binary_dump_metadata(
+ script, denv, sblock, &offset);
+ } T_END;
+
+ if (!success)
+ return FALSE;
+
+ /* Dump list of used extensions */
+
+ count = sieve_binary_extensions_count(sbin);
+ if (count > 0) {
+ sieve_binary_dump_sectionf(
+ denv, "Required extensions (block: %d)",
+ SBIN_SYSBLOCK_EXTENSIONS);
+
+ for (i = 0; i < count; i++) {
+ const struct sieve_extension *ext =
+ sieve_binary_extension_get_by_index(sbin, i);
+
+ sblock = sieve_binary_extension_get_block(sbin, ext);
+
+ if (sblock == NULL) {
+ sieve_binary_dumpf(
+ denv, "%3d: %s (id: %d)\n",
+ i, sieve_extension_name(ext), ext->id);
+ } else {
+ sieve_binary_dumpf(
+ denv, "%3d: %s (id: %d; block: %d)\n",
+ i, sieve_extension_name(ext), ext->id,
+ sieve_binary_block_get_id(sblock));
+ }
+ }
+ }
+
+ /* Dump extension-specific elements of the binary */
+
+ count = sieve_binary_extensions_count(sbin);
+ if (count > 0) {
+ for (i = 0; i < count; i++) {
+ success = TRUE;
+
+ T_BEGIN {
+ const struct sieve_extension *ext =
+ sieve_binary_extension_get_by_index(
+ sbin, i);
+
+ if (ext->def != NULL &&
+ ext->def->binary_dump != NULL) {
+ success = ext->def->binary_dump(
+ ext, denv);
+ }
+ } T_END;
+
+ if (!success)
+ return FALSE;
+ }
+ }
+
+ /* Dump main program */
+
+ sieve_binary_dump_sectionf(denv, "Main program (block: %d)",
+ SBIN_SYSBLOCK_MAIN_PROGRAM);
+
+ dumper->dumpenv.sblock =
+ sieve_binary_block_get(sbin, SBIN_SYSBLOCK_MAIN_PROGRAM);
+ dumper->dumpenv.cdumper = sieve_code_dumper_create(&(dumper->dumpenv));
+
+ if (dumper->dumpenv.cdumper != NULL) {
+ sieve_code_dumper_run(dumper->dumpenv.cdumper);
+
+ sieve_code_dumper_free(&dumper->dumpenv.cdumper);
+ }
+
+ /* Finish with empty line */
+ sieve_binary_dumpf(denv, "\n");
+
+ return TRUE;
+}
+
+/*
+ * Hexdump production
+ */
+
+void sieve_binary_dumper_hexdump(struct sieve_binary_dumper *dumper,
+ struct ostream *stream)
+{
+ struct sieve_binary *sbin = dumper->dumpenv.sbin;
+ struct sieve_dumptime_env *denv = &(dumper->dumpenv);
+ int count, i;
+
+ dumper->dumpenv.stream = stream;
+
+ count = sieve_binary_block_count(sbin);
+
+ /* Block overview */
+
+ sieve_binary_dump_sectionf(denv, "Binary blocks (count: %d)", count);
+
+ for (i = 0; i < count; i++) {
+ struct sieve_binary_block *sblock =
+ sieve_binary_block_get(sbin, i);
+
+ sieve_binary_dumpf(denv, "%3d: size: %zu bytes\n",
+ i, sieve_binary_block_get_size(sblock));
+ }
+
+ /* Hexdump for each block */
+
+ for (i = 0; i < count; i++) {
+ struct sieve_binary_block *sblock =
+ sieve_binary_block_get(sbin, i);
+ buffer_t *blockbuf = sieve_binary_block_get_buffer(sblock);
+ string_t *line;
+ size_t data_size;
+ const unsigned char *data;
+ size_t offset;
+
+ data = buffer_get_data(blockbuf, &data_size);
+
+ // FIXME: calculate offset more nicely.
+ sieve_binary_dump_sectionf(
+ denv, "Block %d (%zu bytes, file offset %08llx)", i, data_size,
+ (unsigned long long int)sblock->offset + 8);
+
+ line = t_str_new(128);
+ offset = 0;
+ while (offset < data_size) {
+ size_t len = (data_size - offset >= 16 ?
+ 16 : data_size - offset);
+ size_t b;
+
+ str_printfa(line, "%08llx ",
+ (unsigned long long)offset);
+
+ for (b = 0; b < len; b++) {
+ str_printfa(line, "%02x ", data[offset+b]);
+ if (b == 7)
+ str_append_c(line, ' ');
+ }
+
+ if (len < 16) {
+ if (len <= 7)
+ str_append_c(line, ' ');
+
+ for (b = len; b < 16; b++)
+ str_append(line, " ");
+ }
+
+ str_append(line, " |");
+
+ for (b = 0; b < len; b++) {
+ const unsigned char c = data[offset+b];
+
+ if (c >= 32 && c <= 126)
+ str_append_c(line, (const char)c);
+ else
+ str_append_c(line, '.');
+ }
+
+ str_append(line, "|\n");
+ o_stream_nsend(stream, str_data(line), str_len(line));
+ str_truncate(line, 0);
+ offset += len;
+ }
+
+ str_printfa(line, "%08llx\n", (unsigned long long)offset);
+ o_stream_nsend(stream, str_data(line), str_len(line));
+ }
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-dumper.h b/pigeonhole/src/lib-sieve/sieve-binary-dumper.h
new file mode 100644
index 0000000..4343190
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-dumper.h
@@ -0,0 +1,41 @@
+#ifndef SIEVE_BINARY_DUMPER_H
+#define SIEVE_BINARY_DUMPER_H
+
+#include "sieve-common.h"
+
+/*
+ * Binary dumper object
+ */
+
+struct sieve_binary_dumper;
+
+struct sieve_binary_dumper *
+sieve_binary_dumper_create(struct sieve_binary *sbin);
+void sieve_binary_dumper_free(struct sieve_binary_dumper **dumper);
+
+pool_t sieve_binary_dumper_pool(struct sieve_binary_dumper *dumper);
+
+/*
+ * Formatted output
+ */
+
+void sieve_binary_dumpf(const struct sieve_dumptime_env *denv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_binary_dump_sectionf(const struct sieve_dumptime_env *denv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+/*
+ * Dumping the binary
+ */
+
+bool sieve_binary_dumper_run(struct sieve_binary_dumper *dumper,
+ struct ostream *stream, bool verbose);
+
+/*
+ * Hexdump production
+ */
+
+void sieve_binary_dumper_hexdump(struct sieve_binary_dumper *dumper,
+ struct ostream *stream);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-file.c b/pigeonhole/src/lib-sieve/sieve-binary-file.c
new file mode 100644
index 0000000..8eedbc2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-file.c
@@ -0,0 +1,1042 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+#include "file-lock.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/*
+ * Macros
+ */
+
+#define SIEVE_BINARY_MAGIC 0xcafebabe
+#define SIEVE_BINARY_MAGIC_OTHER_ENDIAN 0xbebafeca
+
+#define SIEVE_BINARY_ALIGN(offset) \
+ (((offset) + 3) & ~3U)
+#define SIEVE_BINARY_ALIGN_PTR(ptr) \
+ ((void *) SIEVE_BINARY_ALIGN(((size_t) ptr)))
+
+#define SIEVE_BINARY_PRE_HDR_SIZE_MAJOR 1
+#define SIEVE_BINARY_PRE_HDR_SIZE_MINOR 4
+#define SIEVE_BINARY_PRE_HDR_SIZE_HDR_SIZE 12
+
+/*
+ * Header and record structures of the binary on disk
+ */
+
+struct sieve_binary_block_index {
+ uint32_t id;
+ uint32_t size;
+ uint32_t offset;
+ uint32_t ext_id;
+};
+
+struct sieve_binary_block_header {
+ uint32_t id;
+ uint32_t size;
+};
+
+/*
+ * Header manipulation
+ */
+
+static int
+sieve_binary_file_read_header(struct sieve_binary *sbin, int fd,
+ struct sieve_binary_header *header_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_binary_header header;
+ enum sieve_error error;
+ ssize_t rret;
+
+ if (error_r == NULL)
+ error_r = &error;
+ *error_r = SIEVE_ERROR_NONE;
+
+ rret = pread(fd, &header, sizeof(header), 0);
+ if (rret == 0) {
+ e_error(sbin->event, "read: "
+ "file is not large enough to contain the header");
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return -1;
+ } else if (rret < 0) {
+ e_error(sbin->event, "read: "
+ "failed to read from binary: %m");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ } else if (rret != sizeof(header)) {
+ e_error(sbin->event, "read: "
+ "header read only partially %zd/%zu",
+ rret, sizeof(header));
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ /* Check header validity */
+ if (header.magic != SIEVE_BINARY_MAGIC) {
+ if (header.magic != SIEVE_BINARY_MAGIC_OTHER_ENDIAN) {
+ e_error(sbin->event, "read: "
+ "binary has corrupted header "
+ "(0x%08x) or it is not a Sieve binary",
+ header.magic);
+ } else {
+ e_error(sbin->event, "read: "
+ "binary stored with in different endian format "
+ "(automatically fixed when re-compiled)");
+ }
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return -1;
+ }
+ /* Check binary version */
+ if (header.version_major == SIEVE_BINARY_PRE_HDR_SIZE_MAJOR &&
+ header.version_minor == SIEVE_BINARY_PRE_HDR_SIZE_MINOR) {
+ /* Old header without hdr_size; clear new fields */
+ static const size_t old_header_size =
+ SIEVE_BINARY_PRE_HDR_SIZE_HDR_SIZE;
+ memset(PTR_OFFSET(&header, old_header_size), 0,
+ (sizeof(header) - old_header_size));
+ header.hdr_size = old_header_size;
+ } else if (header.version_major != SIEVE_BINARY_VERSION_MAJOR) {
+ /* Binary is of different major version. Caller will have to
+ recompile */
+ e_error(sbin->event, "read: "
+ "binary stored with different major version %d.%d "
+ "(!= %d.%d; automatically fixed when re-compiled)",
+ (int)header.version_major, (int)header.version_minor,
+ SIEVE_BINARY_VERSION_MAJOR, SIEVE_BINARY_VERSION_MINOR);
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return -1;
+ } else if (header.hdr_size < SIEVE_BINARY_BASE_HEADER_SIZE) {
+ /* Header size is smaller than base size */
+ e_error(sbin->event, "read: "
+ "binary is corrupt: header size is too small");
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return -1;
+ }
+ /* Check block content */
+ if (header.blocks == 0) {
+ e_error(sbin->event, "read: "
+ "binary is corrupt: it contains no blocks");
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return -1;
+ }
+ /* Valid */
+ *header_r = header;
+ return 0;
+}
+
+static int
+sieve_binary_file_write_header(struct sieve_binary *sbin, int fd,
+ struct sieve_binary_header *header,
+ enum sieve_error *error_r) ATTR_NULL(4)
+{
+ ssize_t wret;
+
+ wret = pwrite(fd, header, sizeof(*header), 0);
+ if (wret < 0) {
+ e_error(sbin->event, "update: "
+ "failed to write to binary: %m");
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ } else if (wret != sizeof(*header)) {
+ e_error(sbin->event, "update: "
+ "header written partially %zd/%zu",
+ wret, sizeof(*header));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ return 0;
+}
+
+static void sieve_binary_file_update_header(struct sieve_binary *sbin)
+{
+ struct sieve_binary_header *header = &sbin->header;
+ struct sieve_resource_usage rusage;
+
+ sieve_binary_get_resource_usage(sbin, &rusage);
+
+ i_zero(&header->resource_usage);
+ if (HAS_ALL_BITS(header->flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) ||
+ sieve_resource_usage_is_high(sbin->svinst, &rusage)) {
+ header->resource_usage.update_time = ioloop_time;
+ header->resource_usage.cpu_time_msecs = rusage.cpu_time_msecs;
+ }
+
+ sieve_resource_usage_init(&sbin->rusage);
+ sbin->rusage_updated = FALSE;
+
+ (void)sieve_binary_check_resource_usage(sbin);
+}
+
+/*
+ * Saving the binary to a file.
+ */
+
+static inline bool
+_save_skip(struct sieve_binary *sbin, struct ostream *stream, size_t size)
+{
+ if ((o_stream_seek(stream, stream->offset + size)) <= 0) {
+ e_error(sbin->event, "save: "
+ "failed to skip output stream to position "
+ "%"PRIuUOFF_T": %s", stream->offset + size,
+ strerror(stream->stream_errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static inline bool
+_save_skip_aligned(struct sieve_binary *sbin, struct ostream *stream,
+ size_t size, uoff_t *offset)
+{
+ uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
+
+ if ((o_stream_seek(stream, aligned_offset + size)) <= 0) {
+ e_error(sbin->event, "save: "
+ "failed to skip output stream to position "
+ "%"PRIuUOFF_T": %s", aligned_offset + size,
+ strerror(stream->stream_errno));
+ return FALSE;
+ }
+
+ if (offset != NULL)
+ *offset = aligned_offset;
+ return TRUE;
+}
+
+/* FIXME: Is this even necessary for a file? */
+static bool
+_save_full(struct sieve_binary *sbin, struct ostream *stream,
+ const void *data, size_t size)
+{
+ size_t bytes_left = size;
+ const void *pdata = data;
+
+ while (bytes_left > 0) {
+ ssize_t ret;
+
+ ret = o_stream_send(stream, pdata, bytes_left);
+ if (ret <= 0) {
+ e_error(sbin->event, "save: "
+ "failed to write %zu bytes "
+ "to output stream: %s", bytes_left,
+ strerror(stream->stream_errno));
+ return FALSE;
+ }
+
+ pdata = PTR_OFFSET(pdata, ret);
+ bytes_left -= ret;
+ }
+
+ return TRUE;
+}
+
+static bool
+_save_aligned(struct sieve_binary *sbin, struct ostream *stream,
+ const void *data, size_t size, uoff_t *offset)
+{
+ uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
+
+ o_stream_cork(stream);
+
+ /* Align the data by adding zeroes to the output stream */
+ if (stream->offset < aligned_offset) {
+ if (!_save_skip(sbin, stream,
+ (aligned_offset - stream->offset)))
+ return FALSE;
+ }
+
+ if (!_save_full(sbin, stream, data, size))
+ return FALSE;
+
+ o_stream_uncork(stream);
+
+ if (offset != NULL)
+ *offset = aligned_offset;
+ return TRUE;
+}
+
+static bool
+_save_block(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
+{
+ struct sieve_binary_block_header block_header;
+ struct sieve_binary_block *block;
+ const void *data;
+ size_t size;
+
+ block = sieve_binary_block_get(sbin, id);
+ if (block == NULL)
+ return FALSE;
+
+ data = buffer_get_data(block->data, &size);
+
+ block_header.id = id;
+ block_header.size = size;
+
+ if (!_save_aligned(sbin, stream, &block_header, sizeof(block_header),
+ &block->offset))
+ return FALSE;
+
+ return _save_aligned(sbin, stream, data, size, NULL);
+}
+
+static bool
+_save_block_index_record(struct sieve_binary *sbin, struct ostream *stream,
+ unsigned int id)
+{
+ struct sieve_binary_block *block;
+ struct sieve_binary_block_index header;
+
+ block = sieve_binary_block_get(sbin, id);
+ if (block == NULL)
+ return FALSE;
+
+ header.id = id;
+ header.size = buffer_get_used_size(block->data);
+ header.ext_id = block->ext_index;
+ header.offset = block->offset;
+
+ if (!_save_full(sbin, stream, &header, sizeof(header))) {
+ e_error(sbin->event, "save: "
+ "failed to save block index header %d", id);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_binary_save_to_stream(struct sieve_binary *sbin, struct ostream *stream)
+{
+ struct sieve_binary_header *header = &sbin->header;
+ struct sieve_binary_block *ext_block;
+ unsigned int ext_count, blk_count, i;
+ uoff_t block_index;
+
+ blk_count = sieve_binary_block_count(sbin);
+
+ /* Create header */
+
+ header->magic = SIEVE_BINARY_MAGIC;
+ header->version_major = SIEVE_BINARY_VERSION_MAJOR;
+ header->version_minor = SIEVE_BINARY_VERSION_MINOR;
+ header->blocks = blk_count;
+ header->hdr_size = sizeof(*header);
+
+ header->flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+ sieve_binary_file_update_header(sbin);
+
+ if (!_save_aligned(sbin, stream, header, sizeof(*header), NULL)) {
+ e_error(sbin->event, "save: failed to save header");
+ return FALSE;
+ }
+
+ /* Skip block index for now */
+
+ if (!_save_skip_aligned(
+ sbin, stream,
+ (sizeof(struct sieve_binary_block_index) * blk_count),
+ &block_index))
+ return FALSE;
+
+ /* Create block containing all used extensions */
+
+ ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
+ i_assert(ext_block != NULL);
+ sieve_binary_block_clear(ext_block);
+
+ ext_count = array_count(&sbin->linked_extensions);
+ sieve_binary_emit_unsigned(ext_block, ext_count);
+
+ for (i = 0; i < ext_count; i++) {
+ struct sieve_binary_extension_reg * const *ext =
+ array_idx(&sbin->linked_extensions, i);
+
+ sieve_binary_emit_cstring(
+ ext_block, sieve_extension_name((*ext)->extension));
+ sieve_binary_emit_unsigned(
+ ext_block, sieve_extension_version((*ext)->extension));
+ sieve_binary_emit_unsigned(ext_block, (*ext)->block_id);
+ }
+
+ /* Save all blocks into the binary */
+
+ for (i = 0; i < blk_count; i++) {
+ if (!_save_block(sbin, stream, i))
+ return FALSE;
+ }
+
+ /* Create the block index */
+ o_stream_seek(stream, block_index);
+ for (i = 0; i < blk_count; i++) {
+ if (!_save_block_index_record(sbin, stream, i))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+sieve_binary_do_save(struct sieve_binary *sbin, const char *path, bool update,
+ mode_t save_mode, enum sieve_error *error_r)
+{
+ int result, fd;
+ string_t *temp_path;
+ struct ostream *stream;
+ struct sieve_binary_extension_reg *const *regs;
+ unsigned int ext_count, i;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+
+ /* Check whether saving is necessary */
+ if (!update && sbin->path != NULL && strcmp(sbin->path, path) == 0) {
+ e_debug(sbin->event, "save: "
+ "not saving binary, because it is already stored");
+ return 0;
+ }
+
+ /* Open it as temp file first, as not to overwrite an existing just yet */
+ temp_path = t_str_new(256);
+ str_append(temp_path, path);
+ str_append_c(temp_path, '.');
+ fd = safe_mkstemp_hostpid(temp_path, save_mode, (uid_t)-1, (gid_t)-1);
+ if (fd < 0) {
+ if (errno == EACCES) {
+ e_error(sbin->event, "save: "
+ "failed to create temporary file: %s",
+ eacces_error_get_creating("open",
+ str_c(temp_path)));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NO_PERMISSION;
+ } else {
+ e_error(sbin->event, "save: "
+ "failed to create temporary file: "
+ "open(%s) failed: %m", str_c(temp_path));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ }
+ return -1;
+ }
+
+ /* Signal all extensions that we're about to save the binary */
+ regs = array_get(&sbin->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_binary_extension *binext = regs[i]->binext;
+
+ if (binext != NULL && binext->binary_pre_save != NULL &&
+ !binext->binary_pre_save(regs[i]->extension, sbin,
+ regs[i]->context, error_r))
+ return -1;
+ }
+
+ /* Save binary */
+ result = 1;
+ stream = o_stream_create_fd(fd, 0);
+ if (!sieve_binary_save_to_stream(sbin, stream)) {
+ result = -1;
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ }
+ o_stream_destroy(&stream);
+
+ /* Close saved binary */
+ if (close(fd) < 0) {
+ e_error(sbin->event, "save: "
+ "failed to close temporary file: "
+ "close(fd=%s) failed: %m", str_c(temp_path));
+ }
+
+ /* Replace any original binary atomically */
+ if (result > 0 && (rename(str_c(temp_path), path) < 0)) {
+ if (errno == EACCES) {
+ e_error(sbin->event, "save: "
+ "failed to save binary: %s",
+ eacces_error_get_creating("rename", path));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NO_PERMISSION;
+ } else {
+ e_error(sbin->event, "save: "
+ "failed to save binary: "
+ "rename(%s, %s) failed: %m",
+ str_c(temp_path), path);
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ }
+ result = -1;
+ }
+
+ if (result < 0) {
+ /* Get rid of temp output (if any) */
+ if (unlink(str_c(temp_path)) < 0 && errno != ENOENT) {
+ e_error(sbin->event, "save: "
+ "failed to clean up after error: "
+ "unlink(%s) failed: %m", str_c(temp_path));
+ }
+ } else {
+ if (sbin->path == NULL)
+ sbin->path = p_strdup(sbin->pool, path);
+
+ /* Signal all extensions that we successfully saved the binary.
+ */
+ regs = array_get(&sbin->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_binary_extension *binext =
+ regs[i]->binext;
+
+ if (binext != NULL &&
+ binext->binary_post_save != NULL &&
+ !binext->binary_post_save(regs[i]->extension, sbin,
+ regs[i]->context,
+ error_r)) {
+ result = -1;
+ break;
+ }
+ }
+
+ if (result < 0 && unlink(path) < 0 && errno != ENOENT) {
+ e_error(sbin->event, "failed to clean up after error: "
+ "unlink(%s) failed: %m", path);
+ }
+ }
+
+ return result;
+}
+
+int sieve_binary_save(struct sieve_binary *sbin, const char *path, bool update,
+ mode_t save_mode, enum sieve_error *error_r)
+{
+ int ret;
+
+ sieve_binary_update_event(sbin, path);
+ ret = sieve_binary_do_save(sbin, path, update, save_mode, error_r);
+ sieve_binary_update_event(sbin, NULL);
+
+ return ret;
+}
+
+
+/*
+ * Binary file management
+ */
+
+static int
+sieve_binary_fd_open(struct sieve_binary *sbin, const char *path,
+ int open_flags, enum sieve_error *error_r)
+{
+ int fd;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+
+ fd = open(path, open_flags);
+ if (fd < 0) {
+ switch (errno) {
+ case ENOENT:
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ break;
+ case EACCES:
+ e_error(sbin->event, "open: "
+ "failed to open: %s",
+ eacces_error_get("open", path));
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NO_PERMISSION;
+ break;
+ default:
+ e_error(sbin->event, "open: "
+ "failed to open: open(%s) failed: %m", path);
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ break;
+ }
+ return -1;
+ }
+ return fd;
+}
+
+static int
+sieve_binary_file_open(struct sieve_binary *sbin, const char *path,
+ struct sieve_binary_file **file_r,
+ enum sieve_error *error_r)
+{
+ int fd, ret = 0;
+ struct stat st;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+
+ fd = sieve_binary_fd_open(sbin, path, O_RDONLY, error_r);
+ if (fd < 0)
+ return -1;
+
+ if (fstat(fd, &st) < 0) {
+ if (errno != ENOENT)
+ e_error(sbin->event, "open: fstat() failed: %m");
+ ret = -1;
+ }
+
+ if (ret == 0 && !S_ISREG(st.st_mode)) {
+ e_error(sbin->event, "open: "
+ "binary is not a regular file");
+ ret = -1;
+ }
+
+ if (ret < 0) {
+ if (close(fd) < 0) {
+ e_error(sbin->event, "open: "
+ "close() failed after error: %m");
+ }
+ return -1;
+ }
+
+ pool_t pool;
+ struct sieve_binary_file *file;
+
+ pool = pool_alloconly_create("sieve_binary_file", 4096);
+ file = p_new(pool, struct sieve_binary_file, 1);
+ file->pool = pool;
+ file->path = p_strdup(pool, path);
+ file->fd = fd;
+ file->st = st;
+ file->sbin = sbin;
+
+ *file_r = file;
+ return 0;
+}
+
+void sieve_binary_file_close(struct sieve_binary_file **_file)
+{
+ struct sieve_binary_file *file = *_file;
+
+ *_file = NULL;
+ if (file == NULL)
+ return;
+
+ if (file->fd != -1) {
+ if (close(file->fd) < 0) {
+ e_error(file->sbin->event, "close: "
+ "failed to close: close() failed: %m");
+ }
+ }
+
+ pool_unref(&file->pool);
+}
+
+static int
+sieve_binary_file_read(struct sieve_binary_file *file, off_t *offset,
+ void *buffer, size_t size)
+{
+ struct sieve_binary *sbin = file->sbin;
+ int ret;
+ void *indata = buffer;
+ size_t insize = size;
+
+ *offset = SIEVE_BINARY_ALIGN(*offset);
+
+ /* Seek to the correct position */
+ if (*offset != file->offset &&
+ lseek(file->fd, *offset, SEEK_SET) == (off_t)-1) {
+ e_error(sbin->event, "read: "
+ "failed to seek(fd, %lld, SEEK_SET): %m",
+ (long long) *offset);
+ return -1;
+ }
+
+ /* Read record into memory */
+ while (insize > 0) {
+ ret = read(file->fd, indata, insize);
+ if (ret <= 0) {
+ if (ret == 0) {
+ e_error(sbin->event, "read: "
+ "binary is truncated "
+ "(more data expected)");
+ } else {
+ e_error(sbin->event, "read: "
+ "failed to read from binary: %m");
+ }
+ break;
+ }
+
+ indata = PTR_OFFSET(indata, ret);
+ insize -= ret;
+ }
+
+ if (insize != 0) {
+ /* Failed to read the whole requested record */
+ return 0;
+ }
+
+ *offset += size;
+ file->offset = *offset;
+ return 1;
+}
+
+static const void *
+sieve_binary_file_load_data(struct sieve_binary_file *file,
+ off_t *offset, size_t size)
+{
+ void *data = t_malloc_no0(size);
+
+ if (sieve_binary_file_read(file, offset, data, size) > 0)
+ return data;
+
+ return NULL;
+}
+
+static buffer_t *
+sieve_binary_file_load_buffer(struct sieve_binary_file *file,
+ off_t *offset, size_t size)
+{
+ buffer_t *buffer = buffer_create_dynamic(file->pool, size);
+
+ if (sieve_binary_file_read(file, offset,
+ buffer_get_space_unsafe(buffer, 0, size),
+ size) > 0)
+ return buffer;
+
+ return NULL;
+}
+
+/*
+ * Load binary from a file
+ */
+
+#define LOAD_HEADER(sbin, offset, header) \
+ (header *)sieve_binary_file_load_data(sbin->file, offset, \
+ sizeof(header))
+
+bool sieve_binary_load_block(struct sieve_binary_block *sblock)
+{
+ struct sieve_binary *sbin = sblock->sbin;
+ unsigned int id = sblock->id;
+ off_t offset = sblock->offset;
+ const struct sieve_binary_block_header *header =
+ LOAD_HEADER(sbin, &offset,
+ const struct sieve_binary_block_header);
+
+ if (header == NULL) {
+ e_error(sbin->event, "load: binary is corrupt: "
+ "failed to read header of block %d", id);
+ return FALSE;
+ }
+
+ if (header->id != id) {
+ e_error(sbin->event, "load: binary is corrupt: "
+ "header of block %d has non-matching id %d",
+ id, header->id);
+ return FALSE;
+ }
+
+ sblock->data = sieve_binary_file_load_buffer(sbin->file, &offset,
+ header->size);
+ if (sblock->data == NULL) {
+ e_error(sbin->event, "load: "
+ "failed to read block %d of binary (size=%d)",
+ id, header->size);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool
+_read_block_index_record(struct sieve_binary *sbin, off_t *offset,
+ unsigned int id)
+{
+ const struct sieve_binary_block_index *record =
+ LOAD_HEADER(sbin, offset,
+ const struct sieve_binary_block_index);
+ struct sieve_binary_block *block;
+
+ if (record == NULL) {
+ e_error(sbin->event, "open: binary is corrupt: "
+ "failed to load block index record %d", id);
+ return FALSE;
+ }
+
+ if (record->id != id) {
+ e_error(sbin->event, "open: binary is corrupt: "
+ "block index record %d has unexpected id %d",
+ id, record->id);
+ return FALSE;
+ }
+
+ block = sieve_binary_block_create_id(sbin, id);
+ block->ext_index = record->ext_id;
+ block->offset = record->offset;
+
+ return TRUE;
+}
+
+static int _read_extensions(struct sieve_binary_block *sblock)
+{
+ struct sieve_binary *sbin = sblock->sbin;
+ sieve_size_t offset = 0;
+ unsigned int i, count;
+ int result = 1;
+
+ if (!sieve_binary_read_unsigned(sblock, &offset, &count))
+ return -1;
+
+ for (i = 0; result > 0 && i < count; i++) {
+ T_BEGIN {
+ string_t *extension;
+ const struct sieve_extension *ext;
+ unsigned int version;
+
+ if (sieve_binary_read_string(sblock, &offset,
+ &extension)) {
+ ext = sieve_extension_get_by_name(
+ sbin->svinst, str_c(extension));
+
+ if (ext == NULL) {
+ e_error(sbin->event, "open: "
+ "binary requires unknown extension `%s'",
+ str_sanitize(str_c(extension), 128));
+ result = 0;
+ } else {
+ struct sieve_binary_extension_reg *ereg = NULL;
+
+ (void)sieve_binary_extension_register(sbin, ext, &ereg);
+ if (!sieve_binary_read_unsigned(sblock, &offset, &version) ||
+ !sieve_binary_read_unsigned(sblock, &offset, &ereg->block_id)) {
+ result = -1;
+ } else if (!sieve_extension_version_is(ext, version)) {
+ e_debug(sbin->event, "open: "
+ "binary was compiled with different version "
+ "of the `%s' extension (compiled v%d, expected v%d;"
+ "automatically fixed when re-compiled)",
+ sieve_extension_name(ext), version,
+ sieve_extension_version(ext));
+ result = 0;
+ }
+ }
+ } else {
+ result = -1;
+ }
+ } T_END;
+ }
+
+ return result;
+}
+
+static bool
+_sieve_binary_open(struct sieve_binary *sbin, enum sieve_error *error_r)
+{
+ bool result = TRUE;
+ off_t offset = 0;
+ struct sieve_binary_block *ext_block;
+ unsigned int i;
+ int ret;
+
+ /* Read header */
+
+ ret = sieve_binary_file_read_header(sbin, sbin->file->fd,
+ &sbin->header, error_r);
+ if (ret < 0)
+ return FALSE;
+ offset = sbin->header.hdr_size;
+
+ /* Load block index */
+
+ for (i = 0; i < sbin->header.blocks && result; i++) {
+ T_BEGIN {
+ if (!_read_block_index_record(sbin, &offset, i))
+ result = FALSE;
+ } T_END;
+ }
+
+ if (!result) {
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return FALSE;
+ }
+
+ /* Load extensions used by this binary */
+
+ T_BEGIN {
+ ext_block = sieve_binary_block_get(
+ sbin, SBIN_SYSBLOCK_EXTENSIONS);
+ if (ext_block == NULL) {
+ result = FALSE;
+ } else if ((ret = _read_extensions(ext_block)) <= 0) {
+ if (ret < 0) {
+ e_error(sbin->event, "open: binary is corrupt: "
+ "failed to load extension block");
+ }
+ result = FALSE;
+ }
+ } T_END;
+
+ if (!result) {
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+struct sieve_binary *
+sieve_binary_open(struct sieve_instance *svinst, const char *path,
+ struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_binary_extension_reg *const *regs;
+ unsigned int ext_count, i;
+ struct sieve_binary *sbin;
+ struct sieve_binary_file *file;
+
+ i_assert(script == NULL || sieve_script_svinst(script) == svinst);
+
+ /* Create binary object */
+ sbin = sieve_binary_create(svinst, script);
+ sbin->path = p_strdup(sbin->pool, path);
+
+ if (sieve_binary_file_open(sbin, path, &file, error_r) < 0) {
+ sieve_binary_unref(&sbin);
+ return NULL;
+ }
+
+ sbin->file = file;
+
+ event_set_append_log_prefix(
+ sbin->event,
+ t_strdup_printf("binary %s: ", path));
+
+ if (!_sieve_binary_open(sbin, error_r)) {
+ sieve_binary_unref(&sbin);
+ return NULL;
+ }
+
+ sieve_binary_activate(sbin);
+
+ /* Signal open event to extensions */
+ regs = array_get(&sbin->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_binary_extension *binext = regs[i]->binext;
+
+ if (binext != NULL && binext->binary_open != NULL &&
+ !binext->binary_open(regs[i]->extension, sbin,
+ regs[i]->context)) {
+ /* Extension thinks its corrupt */
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ sieve_binary_unref(&sbin);
+ return NULL;
+ }
+ }
+ return sbin;
+}
+
+int sieve_binary_check_executable(struct sieve_binary *sbin,
+ enum sieve_error *error_r,
+ const char **client_error_r)
+{
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ *client_error_r = NULL;
+
+ if (HAS_ALL_BITS(sbin->header.flags,
+ SIEVE_BINARY_FLAG_RESOURCE_LIMIT)) {
+ e_debug(sbin->event,
+ "Binary execution is blocked: "
+ "Cumulative resource usage limit exceeded "
+ "(resource limit flag is set)");
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_RESOURCE_LIMIT;
+ *client_error_r = "cumulative resource usage limit exceeded";
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Resource usage
+ */
+
+static int
+sieve_binary_file_do_update_resource_usage(
+ struct sieve_binary *sbin, int fd, enum sieve_error *error_r)
+{
+ struct sieve_binary_header *header = &sbin->header;
+ struct file_lock *lock;
+ const char *error;
+ int ret;
+
+ struct file_lock_settings lock_set = {
+ .lock_method = FILE_LOCK_METHOD_FCNTL,
+ };
+ ret = file_wait_lock(fd, sbin->path, F_WRLCK, &lock_set,
+ SIEVE_BINARY_FILE_LOCK_TIMEOUT, &lock, &error);
+ if (ret <= 0) {
+ e_error(sbin->event, "%s", error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ ret = sieve_binary_file_read_header(sbin, fd, header, error_r);
+ if (ret == 0) {
+ sieve_binary_file_update_header(sbin);
+ ret = sieve_binary_file_write_header(sbin, fd, header, error_r);
+ }
+
+ file_lock_free(&lock);
+
+ return ret;
+}
+
+int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
+ enum sieve_error *error_r)
+{
+ enum sieve_error error;
+ int fd, ret = 0;
+
+ if (error_r == NULL)
+ error_r = &error;
+ *error_r = SIEVE_ERROR_NONE;
+
+ sieve_binary_file_close(&sbin->file);
+
+ if (sbin->path == NULL)
+ return 0;
+ if (sbin->header.version_major != SIEVE_BINARY_VERSION_MAJOR)
+ return sieve_binary_save(sbin, sbin->path, TRUE, 0600, error_r);
+
+ fd = sieve_binary_fd_open(sbin, sbin->path, O_RDWR, error_r);
+ if (fd < 0)
+ return -1;
+
+ ret = sieve_binary_file_do_update_resource_usage(sbin, fd, error_r);
+
+ if (close(fd) < 0) {
+ e_error(sbin->event, "update: "
+ "failed to close: close() failed: %m");
+ }
+
+ return ret;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-binary-private.h b/pigeonhole/src/lib-sieve/sieve-binary-private.h
new file mode 100644
index 0000000..73d2d75
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary-private.h
@@ -0,0 +1,240 @@
+#ifndef SIEVE_BINARY_PRIVATE_H
+#define SIEVE_BINARY_PRIVATE_H
+
+#include "sieve-common.h"
+#include "sieve-binary.h"
+#include "sieve-extensions.h"
+
+#include <sys/stat.h>
+
+#define SIEVE_BINARY_FILE_LOCK_TIMEOUT 10
+
+/*
+ * Binary file
+ */
+
+enum SIEVE_BINARY_FLAGS {
+ SIEVE_BINARY_FLAG_RESOURCE_LIMIT = BIT(0),
+};
+
+struct sieve_binary_header {
+ uint32_t magic;
+ uint16_t version_major;
+ uint16_t version_minor;
+ uint32_t blocks;
+
+ uint32_t hdr_size;
+ uint32_t flags;
+
+ struct {
+ uint64_t update_time;
+ uint32_t cpu_time_msecs;
+ } resource_usage;
+};
+
+struct sieve_binary_file {
+ pool_t pool;
+ const char *path;
+ struct sieve_binary *sbin;
+
+ struct stat st;
+ int fd;
+ off_t offset;
+};
+
+void sieve_binary_file_close(struct sieve_binary_file **_file);
+
+/*
+ * Internal structures
+ */
+
+/* Extension registration */
+
+struct sieve_binary_extension_reg {
+ /* The identifier of the extension within this binary */
+ int index;
+
+ /* Global extension object */
+ const struct sieve_extension *extension;
+
+ /* Extension to the binary; typically used to manage extension-specific
+ blocks in the binary and as a means to get a binary_free notification
+ to release references held by extensions.
+ */
+ const struct sieve_binary_extension *binext;
+
+ /* Context data associated to the binary by this extension */
+ void *context;
+
+ /* Main block for this extension */
+ unsigned int block_id;
+};
+
+/* Block */
+
+struct sieve_binary_block {
+ struct sieve_binary *sbin;
+ unsigned int id;
+ int ext_index;
+
+ buffer_t *data;
+
+ uoff_t offset;
+};
+
+/*
+ * Binary object
+ */
+
+struct sieve_binary {
+ pool_t pool;
+ int refcount;
+ struct sieve_instance *svinst;
+ struct event *event;
+
+ struct sieve_script *script;
+
+ struct sieve_binary_file *file;
+ struct sieve_binary_header header;
+ struct sieve_resource_usage rusage;
+
+ /* When the binary is loaded into memory or when it is being constructed
+ by the generator, extensions can be associated to the binary. The
+ extensions array is a sequential list of all linked extensions. The
+ extension_index array is a mapping ext_id -> binary_extension. This
+ is used to obtain the index code associated with an extension for
+ this particular binary. The linked_extensions list all extensions
+ linked to this binary object other than the preloaded language
+ features implemented as 'extensions'.
+
+ All arrays refer to the same extension registration objects. Upon
+ loading a binary, the 'require'd extensions will sometimes need to
+ associate context data to the binary object in memory. This is stored
+ in these registration objects as well.
+ */
+ ARRAY(struct sieve_binary_extension_reg *) extensions;
+ ARRAY(struct sieve_binary_extension_reg *) extension_index;
+ ARRAY(struct sieve_binary_extension_reg *) linked_extensions;
+
+ /* Attributes of a loaded binary */
+ const char *path;
+
+ /* Blocks */
+ ARRAY(struct sieve_binary_block *) blocks;
+
+ bool rusage_updated:1;
+};
+
+void sieve_binary_update_event(struct sieve_binary *sbin, const char *new_path)
+ ATTR_NULL(2);
+
+struct sieve_binary *
+sieve_binary_create(struct sieve_instance *svinst, struct sieve_script *script);
+
+/* Blocks management */
+
+static inline struct sieve_binary_block *
+sieve_binary_block_index(struct sieve_binary *sbin, unsigned int id)
+{
+ struct sieve_binary_block * const *sblock;
+
+ if (id >= array_count(&sbin->blocks))
+ return NULL;
+
+ sblock = array_idx(&sbin->blocks, id);
+ if (*sblock == NULL)
+ return NULL;
+ return *sblock;
+}
+
+static inline size_t
+_sieve_binary_block_get_size(const struct sieve_binary_block *sblock)
+{
+ return buffer_get_used_size(sblock->data);
+}
+
+struct sieve_binary_block *
+sieve_binary_block_create_id(struct sieve_binary *sbin, unsigned int id);
+
+buffer_t *sieve_binary_block_get_buffer(struct sieve_binary_block *sblock);
+
+/* Extension registration */
+
+static inline struct sieve_binary_extension_reg *
+sieve_binary_extension_create_reg(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ int index = array_count(&sbin->extensions);
+ struct sieve_binary_extension_reg *ereg;
+
+ if (ext->id < 0)
+ return NULL;
+
+ ereg = p_new(sbin->pool, struct sieve_binary_extension_reg, 1);
+ ereg->index = index;
+ ereg->extension = ext;
+
+ array_idx_set(&sbin->extensions, (unsigned int) index, &ereg);
+ array_idx_set(&sbin->extension_index, (unsigned int) ext->id, &ereg);
+
+ return ereg;
+}
+
+static inline struct sieve_binary_extension_reg *
+sieve_binary_extension_get_reg(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ bool create)
+{
+ struct sieve_binary_extension_reg *reg = NULL;
+
+ if (ext->id >= 0 &&
+ ext->id < (int)array_count(&sbin->extension_index)) {
+ struct sieve_binary_extension_reg * const *ereg =
+ array_idx(&sbin->extension_index,
+ (unsigned int)ext->id);
+
+ reg = *ereg;
+ }
+
+ /* Register if not known */
+ if (reg == NULL && create)
+ return sieve_binary_extension_create_reg(sbin, ext);
+ return reg;
+}
+
+static inline int
+sieve_binary_extension_register(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ struct sieve_binary_extension_reg **reg_r)
+{
+ struct sieve_binary_extension_reg *ereg;
+
+ if ((ereg = sieve_binary_extension_get_reg(sbin, ext, FALSE)) == NULL) {
+ ereg = sieve_binary_extension_create_reg(sbin, ext);
+
+ if (ereg == NULL)
+ return -1;
+
+ array_append(&sbin->linked_extensions, &ereg, 1);
+ }
+
+ if (reg_r != NULL)
+ *reg_r = ereg;
+ return ereg->index;
+}
+
+/* Load/Save */
+
+bool sieve_binary_load_block(struct sieve_binary_block *);
+
+/*
+ * Resource limits
+ */
+
+bool sieve_binary_check_resource_usage(struct sieve_binary *sbin);
+
+int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
+ enum sieve_error *error_r)
+ ATTR_NULL(2);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-binary.c b/pigeonhole/src/lib-sieve/sieve-binary.c
new file mode 100644
index 0000000..06cf598
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary.c
@@ -0,0 +1,606 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "hash.h"
+#include "array.h"
+#include "ostream.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-script.h"
+
+#include "sieve-binary-private.h"
+
+/*
+ * Forward declarations
+ */
+
+static inline struct sieve_binary_extension_reg *
+sieve_binary_extension_get_reg(struct sieve_binary *sbin,
+ const struct sieve_extension *ext, bool create);
+
+static inline int
+sieve_binary_extension_register(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ struct sieve_binary_extension_reg **reg);
+
+/*
+ * Binary object
+ */
+
+void sieve_binary_update_event(struct sieve_binary *sbin, const char *new_path)
+{
+ if (new_path != NULL) {
+ event_set_append_log_prefix(
+ sbin->event, t_strdup_printf("binary %s: ", new_path));
+ } else if (sbin->path != NULL) {
+ event_set_append_log_prefix(
+ sbin->event,
+ t_strdup_printf("binary %s: ", sbin->path));
+ } else if (sbin->script != NULL) {
+ event_set_append_log_prefix(
+ sbin->event,
+ t_strdup_printf("binary %s: ",
+ sieve_script_name(sbin->script)));
+ } else {
+ event_set_append_log_prefix(sbin->event, "binary: ");
+ }
+}
+
+struct sieve_binary *
+sieve_binary_create(struct sieve_instance *svinst, struct sieve_script *script)
+{
+ pool_t pool;
+ struct sieve_binary *sbin;
+ const struct sieve_extension *const *ext_preloaded;
+ unsigned int i, ext_count;
+
+ pool = pool_alloconly_create("sieve_binary", 16384);
+ sbin = p_new(pool, struct sieve_binary, 1);
+ sbin->pool = pool;
+ sbin->refcount = 1;
+ sbin->svinst = svinst;
+
+ sbin->header.version_major = SIEVE_BINARY_VERSION_MAJOR;
+ sbin->header.version_minor = SIEVE_BINARY_VERSION_MINOR;
+
+ sbin->script = script;
+ if (script != NULL)
+ sieve_script_ref(script);
+
+ sbin->event = event_create(svinst->event);
+
+ ext_count = sieve_extensions_get_count(svinst);
+
+ p_array_init(&sbin->linked_extensions, pool, ext_count);
+ p_array_init(&sbin->extensions, pool, ext_count);
+ p_array_init(&sbin->extension_index, pool, ext_count);
+
+ p_array_init(&sbin->blocks, pool, 16);
+
+ /* Pre-load core language features implemented as 'extensions' */
+ ext_preloaded = sieve_extensions_get_preloaded(svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension_def *ext_def = ext_preloaded[i]->def;
+
+ if (ext_def != NULL && ext_def->binary_load != NULL)
+ (void)ext_def->binary_load(ext_preloaded[i], sbin);
+ }
+
+ return sbin;
+}
+
+struct sieve_binary *sieve_binary_create_new(struct sieve_script *script)
+{
+ struct sieve_binary *sbin =
+ sieve_binary_create(sieve_script_svinst(script), script);
+ struct sieve_binary_block *sblock;
+ unsigned int i;
+
+ sieve_binary_update_event(sbin, NULL);
+
+ /* Create script metadata block */
+ sblock = sieve_binary_block_create(sbin);
+ sieve_script_binary_write_metadata(script, sblock);
+
+ /* Create other system blocks */
+ for (i = 1; i < SBIN_SYSBLOCK_LAST; i++)
+ (void)sieve_binary_block_create(sbin);
+
+ return sbin;
+}
+
+void sieve_binary_ref(struct sieve_binary *sbin)
+{
+ sbin->refcount++;
+}
+
+static inline void sieve_binary_extensions_free(struct sieve_binary *sbin)
+{
+ struct sieve_binary_extension_reg *const *regs;
+ unsigned int ext_count, i;
+
+ /* Cleanup binary extensions */
+ regs = array_get(&sbin->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_binary_extension *binext = regs[i]->binext;
+
+ if (binext != NULL && binext->binary_free != NULL) {
+ binext->binary_free(regs[i]->extension, sbin,
+ regs[i]->context);
+ }
+ }
+}
+
+static void sieve_binary_update_resource_usage(struct sieve_binary *sbin)
+{
+ enum sieve_error error;
+
+ if (sbin->rusage_updated)
+ (void)sieve_binary_file_update_resource_usage(sbin, &error);
+ sbin->rusage_updated = FALSE;
+}
+
+void sieve_binary_unref(struct sieve_binary **_sbin)
+{
+ struct sieve_binary *sbin = *_sbin;
+
+ *_sbin = NULL;
+ if (sbin == NULL)
+ return;
+
+ i_assert(sbin->refcount > 0);
+ if (--sbin->refcount != 0)
+ return;
+
+ sieve_binary_file_close(&sbin->file);
+ sieve_binary_update_resource_usage(sbin);
+ sieve_binary_extensions_free(sbin);
+
+ if (sbin->script != NULL)
+ sieve_script_unref(&sbin->script);
+
+ event_unref(&sbin->event);
+ pool_unref(&sbin->pool);
+}
+
+void sieve_binary_close(struct sieve_binary **_sbin)
+{
+ struct sieve_binary *sbin = *_sbin;
+
+ *_sbin = NULL;
+ if (sbin == NULL)
+ return;
+
+ sieve_binary_file_close(&sbin->file);
+ sieve_binary_update_resource_usage(sbin);
+ sieve_binary_unref(&sbin);
+}
+
+/*
+ * Resource usage
+ */
+
+void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
+ struct sieve_resource_usage *rusage_r)
+{
+ struct sieve_binary_header *header = &sbin->header;
+ time_t update_time = header->resource_usage.update_time;
+ unsigned int timeout = sbin->svinst->resource_usage_timeout_secs;
+
+ if (update_time != 0 && (ioloop_time - update_time) > timeout)
+ i_zero(&header->resource_usage);
+
+ sieve_resource_usage_init(rusage_r);
+ rusage_r->cpu_time_msecs = header->resource_usage.cpu_time_msecs;
+ sieve_resource_usage_add(rusage_r, &sbin->rusage);
+}
+
+bool sieve_binary_check_resource_usage(struct sieve_binary *sbin)
+{
+ struct sieve_binary_header *header = &sbin->header;
+ struct sieve_resource_usage rusage;
+
+ sieve_binary_get_resource_usage(sbin, &rusage);
+
+ if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
+ header->flags |= SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool sieve_binary_record_resource_usage(
+ struct sieve_binary *sbin, const struct sieve_resource_usage *rusage)
+{
+ struct sieve_resource_usage rusage_total;
+
+ if (sbin == NULL)
+ return TRUE;
+ if (!sieve_resource_usage_is_high(sbin->svinst, rusage))
+ return TRUE;
+
+ sieve_resource_usage_add(&sbin->rusage, rusage);
+ sbin->rusage_updated = TRUE;
+
+ sieve_binary_get_resource_usage(sbin, &rusage_total);
+
+ e_debug(sbin->event, "Updated cumulative resource usage: %s",
+ sieve_resource_usage_get_summary(&rusage_total));
+
+ return sieve_binary_check_resource_usage(sbin);
+}
+
+void sieve_binary_set_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage)
+{
+ struct sieve_binary_header *header = &sbin->header;
+
+ i_zero(&header->resource_usage);
+ sbin->rusage = *rusage;
+ sbin->rusage_updated = TRUE;
+
+ (void)sieve_binary_check_resource_usage(sbin);
+}
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_binary_pool(struct sieve_binary *sbin)
+{
+ return sbin->pool;
+}
+
+struct sieve_script *sieve_binary_script(struct sieve_binary *sbin)
+{
+ return sbin->script;
+}
+
+const char *sieve_binary_path(struct sieve_binary *sbin)
+{
+ return sbin->path;
+}
+
+bool sieve_binary_saved(struct sieve_binary *sbin)
+{
+ return (sbin->path != NULL);
+}
+
+bool sieve_binary_loaded(struct sieve_binary *sbin)
+{
+ return (sbin->file != NULL);
+}
+
+const char *sieve_binary_source(struct sieve_binary *sbin)
+{
+ if (sbin->script != NULL && (sbin->path == NULL || sbin->file == NULL))
+ return sieve_script_location(sbin->script);
+
+ return sbin->path;
+}
+
+struct sieve_instance *sieve_binary_svinst(struct sieve_binary *sbin)
+{
+ return sbin->svinst;
+}
+
+time_t sieve_binary_mtime(struct sieve_binary *sbin)
+{
+ i_assert(sbin->file != NULL);
+ return sbin->file->st.st_mtime;
+}
+
+const struct stat *sieve_binary_stat(struct sieve_binary *sbin)
+{
+ i_assert(sbin->file != NULL);
+ return &sbin->file->st;
+}
+
+const char *sieve_binary_script_name(struct sieve_binary *sbin)
+{
+ return (sbin->script == NULL ?
+ NULL : sieve_script_name(sbin->script));
+}
+
+const char *sieve_binary_script_location(struct sieve_binary *sbin)
+{
+ return (sbin->script == NULL ?
+ NULL : sieve_script_location(sbin->script));
+}
+
+/*
+ * Utility
+ */
+
+const char *sieve_binfile_from_name(const char *name)
+{
+ return t_strconcat(name, "."SIEVE_BINARY_FILEEXT, NULL);
+}
+
+/*
+ * Block management
+ */
+
+unsigned int sieve_binary_block_count(struct sieve_binary *sbin)
+{
+ return array_count(&sbin->blocks);
+}
+
+struct sieve_binary_block *sieve_binary_block_create(struct sieve_binary *sbin)
+{
+ unsigned int id = sieve_binary_block_count(sbin);
+ struct sieve_binary_block *sblock;
+
+ sblock = p_new(sbin->pool, struct sieve_binary_block, 1);
+ sblock->data = buffer_create_dynamic(sbin->pool, 64);
+ sblock->sbin = sbin;
+ sblock->id = id;
+
+ array_append(&sbin->blocks, &sblock, 1);
+
+ return sblock;
+}
+
+struct sieve_binary_block *
+sieve_binary_block_create_id(struct sieve_binary *sbin, unsigned int id)
+{
+ struct sieve_binary_block *sblock;
+
+ sblock = p_new(sbin->pool, struct sieve_binary_block, 1);
+
+ array_idx_set(&sbin->blocks, id, &sblock);
+ sblock->data = NULL;
+ sblock->sbin = sbin;
+ sblock->id = id;
+
+ return sblock;
+}
+
+static bool sieve_binary_block_fetch(struct sieve_binary_block *sblock)
+{
+ struct sieve_binary *sbin = sblock->sbin;
+
+ if (sbin->file != NULL) {
+ /* Try to acces the block in the binary on disk (apparently we
+ were lazy)
+ */
+ if (!sieve_binary_load_block(sblock) || sblock->data == NULL)
+ return FALSE;
+ } else {
+ sblock->data = buffer_create_dynamic(sbin->pool, 64);
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+struct sieve_binary_block *
+sieve_binary_block_get(struct sieve_binary *sbin, unsigned int id)
+{
+ struct sieve_binary_block *sblock = sieve_binary_block_index(sbin, id);
+
+ if (sblock == NULL)
+ return NULL;
+
+ if (sblock->data == NULL && !sieve_binary_block_fetch(sblock))
+ return NULL;
+
+ return sblock;
+}
+
+void sieve_binary_block_clear(struct sieve_binary_block *sblock)
+{
+ buffer_set_used_size(sblock->data, 0);
+}
+
+buffer_t *sieve_binary_block_get_buffer(struct sieve_binary_block *sblock)
+{
+ if (sblock->data == NULL && !sieve_binary_block_fetch(sblock))
+ return NULL;
+
+ return sblock->data;
+}
+
+struct sieve_binary *
+sieve_binary_block_get_binary(const struct sieve_binary_block *sblock)
+{
+ return sblock->sbin;
+}
+
+unsigned int sieve_binary_block_get_id(const struct sieve_binary_block *sblock)
+{
+ return sblock->id;
+}
+
+size_t sieve_binary_block_get_size(const struct sieve_binary_block *sblock)
+{
+ return _sieve_binary_block_get_size(sblock);
+}
+
+/*
+ * Up-to-date checking
+ */
+
+bool sieve_binary_up_to_date(struct sieve_binary *sbin,
+ enum sieve_compile_flags cpflags)
+{
+ struct sieve_binary_extension_reg *const *regs;
+ struct sieve_binary_block *sblock;
+ sieve_size_t offset = 0;
+ unsigned int ext_count, i;
+ int ret;
+
+ i_assert(sbin->file != NULL);
+
+ sblock = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_SCRIPT_DATA);
+ if (sblock == NULL || sbin->script == NULL)
+ return FALSE;
+
+ if ((ret = sieve_script_binary_read_metadata(sbin->script, sblock,
+ &offset)) <= 0) {
+ if (ret < 0) {
+ e_debug(sbin->event, "up-to-date: "
+ "failed to read script metadata from binary");
+ } else {
+ e_debug(sbin->event, "up-to-date: "
+ "script metadata indicates that binary is not up-to-date");
+ }
+ return FALSE;
+ }
+
+ regs = array_get(&sbin->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_binary_extension *binext = regs[i]->binext;
+
+ if (binext != NULL && binext->binary_up_to_date != NULL &&
+ !binext->binary_up_to_date(regs[i]->extension, sbin,
+ regs[i]->context, cpflags)) {
+ e_debug(sbin->event, "up-to-date: "
+ "the %s extension indicates binary is not up-to-date",
+ sieve_extension_name(regs[i]->extension));
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/*
+ * Activate the binary (after code generation)
+ */
+
+void sieve_binary_activate(struct sieve_binary *sbin)
+{
+ struct sieve_binary_extension_reg *const *regs;
+ unsigned int i, ext_count;
+
+ /* Load other extensions into binary */
+ regs = array_get(&sbin->linked_extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension *ext = regs[i]->extension;
+
+ if (ext != NULL && ext->def != NULL &&
+ ext->def->binary_load != NULL)
+ ext->def->binary_load(ext, sbin);
+ }
+}
+
+/*
+ * Extension handling
+ */
+
+void sieve_binary_extension_set_context(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+ if (ereg != NULL)
+ ereg->context = context;
+}
+
+const void *
+sieve_binary_extension_get_context(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+ if (ereg != NULL)
+ return ereg->context;
+
+ return NULL;
+}
+
+void sieve_binary_extension_set(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ const struct sieve_binary_extension *bext,
+ void *context)
+{
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+ if (ereg != NULL) {
+ ereg->binext = bext;
+
+ if (context != NULL)
+ ereg->context = context;
+ }
+}
+
+struct sieve_binary_block *
+sieve_binary_extension_create_block(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ struct sieve_binary_block *sblock;
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+ i_assert(ereg != NULL);
+
+ sblock = sieve_binary_block_create(sbin);
+
+ if (ereg->block_id < SBIN_SYSBLOCK_LAST)
+ ereg->block_id = sblock->id;
+ sblock->ext_index = ereg->index;
+
+ return sblock;
+}
+
+struct sieve_binary_block *
+sieve_binary_extension_get_block(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, TRUE);
+
+ i_assert(ereg != NULL);
+
+ if (ereg->block_id < SBIN_SYSBLOCK_LAST)
+ return NULL;
+
+ return sieve_binary_block_get(sbin, ereg->block_id);
+}
+
+int sieve_binary_extension_link(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ return sieve_binary_extension_register(sbin, ext, NULL);
+}
+
+const struct sieve_extension *
+sieve_binary_extension_get_by_index(struct sieve_binary *sbin, int index)
+{
+ struct sieve_binary_extension_reg * const *ereg;
+
+ if (index < (int)array_count(&sbin->extensions)) {
+ ereg = array_idx(&sbin->extensions, (unsigned int)index);
+
+ return (*ereg)->extension;
+ }
+
+ return NULL;
+}
+
+int sieve_binary_extension_get_index(struct sieve_binary *sbin,
+ const struct sieve_extension *ext)
+{
+ struct sieve_binary_extension_reg *ereg =
+ sieve_binary_extension_get_reg(sbin, ext, FALSE);
+
+ return (ereg == NULL ? -1 : ereg->index);
+}
+
+int sieve_binary_extensions_count(struct sieve_binary *sbin)
+{
+ return (int)array_count(&sbin->extensions);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-binary.h b/pigeonhole/src/lib-sieve/sieve-binary.h
new file mode 100644
index 0000000..0b8b66e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-binary.h
@@ -0,0 +1,291 @@
+#ifndef SIEVE_BINARY_H
+#define SIEVE_BINARY_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Config
+ */
+
+#define SIEVE_BINARY_VERSION_MAJOR 2
+#define SIEVE_BINARY_VERSION_MINOR 0
+
+#define SIEVE_BINARY_BASE_HEADER_SIZE 20
+
+/*
+ * Binary object
+ */
+
+struct sieve_binary;
+
+struct sieve_binary *sieve_binary_create_new(struct sieve_script *script);
+void sieve_binary_ref(struct sieve_binary *sbin);
+void sieve_binary_unref(struct sieve_binary **_sbin);
+
+void sieve_binary_close(struct sieve_binary **_sbin);
+
+/*
+ * Resource usage
+ */
+
+void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
+ struct sieve_resource_usage *rusage_r);
+bool sieve_binary_record_resource_usage(
+ struct sieve_binary *sbin, const struct sieve_resource_usage *rusage)
+ ATTR_NULL(1);
+void sieve_binary_set_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage);
+/*
+ * Accessors
+ */
+
+pool_t sieve_binary_pool(struct sieve_binary *sbin);
+struct sieve_instance *sieve_binary_svinst(struct sieve_binary *sbin);
+const char *sieve_binary_path(struct sieve_binary *sbin);
+struct sieve_script *sieve_binary_script(struct sieve_binary *sbin);
+
+time_t sieve_binary_mtime(struct sieve_binary *sbin);
+const struct stat *sieve_binary_stat(struct sieve_binary *sbin);
+
+const char *sieve_binary_script_name(struct sieve_binary *sbin);
+const char *sieve_binary_script_location(struct sieve_binary *sbin);
+
+const char *sieve_binary_source(struct sieve_binary *sbin);
+bool sieve_binary_loaded(struct sieve_binary *sbin);
+bool sieve_binary_saved(struct sieve_binary *sbin);
+
+/*
+ * Utility
+ */
+
+const char *sieve_binfile_from_name(const char *name);
+
+/*
+ * Activation after code generation
+ */
+
+void sieve_binary_activate(struct sieve_binary *sbin);
+
+/*
+ * Saving the binary
+ */
+
+int sieve_binary_save(struct sieve_binary *sbin, const char *path, bool update,
+ mode_t save_mode, enum sieve_error *error_r);
+
+/*
+ * Loading the binary
+ */
+
+struct sieve_binary *
+sieve_binary_open(struct sieve_instance *svinst, const char *path,
+ struct sieve_script *script, enum sieve_error *error_r);
+bool sieve_binary_up_to_date(struct sieve_binary *sbin,
+ enum sieve_compile_flags cpflags);
+
+int sieve_binary_check_executable(struct sieve_binary *sbin,
+ enum sieve_error *error_r,
+ const char **client_error_r);
+
+/*
+ * Block management
+ */
+
+enum sieve_binary_system_block {
+ SBIN_SYSBLOCK_SCRIPT_DATA,
+ SBIN_SYSBLOCK_EXTENSIONS,
+ SBIN_SYSBLOCK_MAIN_PROGRAM,
+ SBIN_SYSBLOCK_LAST
+};
+
+struct sieve_binary_block *sieve_binary_block_create(struct sieve_binary *sbin);
+
+unsigned int sieve_binary_block_count(struct sieve_binary *sbin);
+
+struct sieve_binary_block *
+sieve_binary_block_get(struct sieve_binary *sbin, unsigned int id);
+
+void sieve_binary_block_clear(struct sieve_binary_block *sblock);
+
+size_t sieve_binary_block_get_size(const struct sieve_binary_block *sblock);
+
+struct sieve_binary *
+sieve_binary_block_get_binary(const struct sieve_binary_block *sblock);
+
+unsigned int sieve_binary_block_get_id(const struct sieve_binary_block *sblock);
+
+/*
+ * Extension support
+ */
+
+struct sieve_binary_extension {
+ const struct sieve_extension_def *extension;
+
+ bool (*binary_pre_save)(const struct sieve_extension *ext,
+ struct sieve_binary *sbin, void *context,
+ enum sieve_error *error_r);
+ bool (*binary_post_save)(const struct sieve_extension *ext,
+ struct sieve_binary *sbin, void *context,
+ enum sieve_error *error_r);
+ bool (*binary_open)(const struct sieve_extension *ext,
+ struct sieve_binary *sbin, void *context);
+
+ void (*binary_free)(const struct sieve_extension *ext,
+ struct sieve_binary *sbin, void *context);
+
+ bool (*binary_up_to_date)(const struct sieve_extension *ext,
+ struct sieve_binary *sbin, void *context,
+ enum sieve_compile_flags cpflags);
+};
+
+void sieve_binary_extension_set_context(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ void *context);
+const void *
+sieve_binary_extension_get_context(struct sieve_binary *sbin,
+ const struct sieve_extension *ext);
+
+void sieve_binary_extension_set(struct sieve_binary *sbin,
+ const struct sieve_extension *ext,
+ const struct sieve_binary_extension *bext,
+ void *context);
+
+struct sieve_binary_block *
+sieve_binary_extension_create_block(struct sieve_binary *sbin,
+ const struct sieve_extension *ext);
+struct sieve_binary_block *
+sieve_binary_extension_get_block(struct sieve_binary *sbin,
+ const struct sieve_extension *ext);
+
+int sieve_binary_extension_link(struct sieve_binary *sbin,
+ const struct sieve_extension *ext);
+const struct sieve_extension *
+sieve_binary_extension_get_by_index(struct sieve_binary *sbin, int index);
+int sieve_binary_extension_get_index(struct sieve_binary *sbin,
+ const struct sieve_extension *ext);
+int sieve_binary_extensions_count(struct sieve_binary *sbin);
+
+/*
+ * Code emission
+ */
+
+/* Low-level emission functions */
+
+sieve_size_t sieve_binary_emit_data(struct sieve_binary_block *sblock,
+ const void *data, sieve_size_t size);
+sieve_size_t sieve_binary_emit_byte(struct sieve_binary_block *sblock,
+ uint8_t byte);
+void sieve_binary_update_data(struct sieve_binary_block *sblock,
+ sieve_size_t address, const void *data,
+ sieve_size_t size);
+
+/* Offset emission functions */
+
+sieve_size_t sieve_binary_emit_offset(struct sieve_binary_block *sblock,
+ sieve_offset_t offset);
+void sieve_binary_resolve_offset(struct sieve_binary_block *sblock,
+ sieve_size_t address);
+
+/* Literal emission functions */
+
+sieve_size_t sieve_binary_emit_integer(struct sieve_binary_block *sblock,
+ sieve_number_t integer);
+sieve_size_t sieve_binary_emit_string(struct sieve_binary_block *sblock,
+ const string_t *str);
+sieve_size_t sieve_binary_emit_cstring(struct sieve_binary_block *sblock,
+ const char *str);
+
+static inline sieve_size_t
+sieve_binary_emit_unsigned(struct sieve_binary_block *sblock,
+ unsigned int count)
+{
+ return sieve_binary_emit_integer(sblock, count);
+}
+
+/* Extension emission functions */
+
+sieve_size_t sieve_binary_emit_extension(struct sieve_binary_block *sblock,
+ const struct sieve_extension *ext,
+ unsigned int offset);
+void sieve_binary_emit_extension_object(
+ struct sieve_binary_block *sblock,
+ const struct sieve_extension_objects *objs, unsigned int code);
+
+/*
+ * Code retrieval
+ */
+
+/* Literals */
+
+bool sieve_binary_read_byte(struct sieve_binary_block *sblock,
+ sieve_size_t *address, unsigned int *byte_r)
+ ATTR_NULL(3);
+bool sieve_binary_read_code(struct sieve_binary_block *sblock,
+ sieve_size_t *address, signed int *code_r)
+ ATTR_NULL(3);
+bool sieve_binary_read_offset(struct sieve_binary_block *sblock,
+ sieve_size_t *address, sieve_offset_t *offset_r)
+ ATTR_NULL(3);
+bool sieve_binary_read_integer(struct sieve_binary_block *sblock,
+ sieve_size_t *address, sieve_number_t *int_r)
+ ATTR_NULL(3);
+bool sieve_binary_read_string(struct sieve_binary_block *sblock,
+ sieve_size_t *address, string_t **str_r)
+ ATTR_NULL(3);
+
+static inline bool ATTR_NULL(3)
+sieve_binary_read_unsigned(struct sieve_binary_block *sblock,
+ sieve_size_t *address, unsigned int *count_r)
+{
+ sieve_number_t integer = 0;
+
+ if (!sieve_binary_read_integer(sblock, address, &integer))
+ return FALSE;
+ if (count_r != NULL)
+ *count_r = integer;
+ return TRUE;
+}
+
+/* Extensions */
+
+bool sieve_binary_read_extension(struct sieve_binary_block *sblock,
+ sieve_size_t *address, unsigned int *offset_r,
+ const struct sieve_extension **ext_r);
+const void *
+sieve_binary_read_extension_object(struct sieve_binary_block *sblock,
+ sieve_size_t *address,
+ const struct sieve_extension_objects *objs);
+
+/*
+ * Debug info
+ */
+
+/* Writer */
+
+struct sieve_binary_debug_writer;
+
+struct sieve_binary_debug_writer *
+sieve_binary_debug_writer_init(struct sieve_binary_block *sblock);
+void sieve_binary_debug_writer_deinit(
+ struct sieve_binary_debug_writer **dwriter);
+
+void sieve_binary_debug_emit(struct sieve_binary_debug_writer *dwriter,
+ sieve_size_t code_address, unsigned int code_line,
+ unsigned int code_column);
+
+/* Reader */
+
+struct sieve_binary_debug_reader *
+sieve_binary_debug_reader_init(struct sieve_binary_block *sblock);
+void sieve_binary_debug_reader_deinit(
+ struct sieve_binary_debug_reader **dreader);
+
+void sieve_binary_debug_reader_reset(struct sieve_binary_debug_reader *dreader);
+
+unsigned int
+sieve_binary_debug_read_line(struct sieve_binary_debug_reader *dreader,
+ sieve_size_t code_address);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-code-dumper.c b/pigeonhole/src/lib-sieve/sieve-code-dumper.c
new file mode 100644
index 0000000..17e2768
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-code-dumper.c
@@ -0,0 +1,351 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-comparators.h"
+
+#include "sieve-dump.h"
+
+/*
+ * Code dumper extension
+ */
+
+struct sieve_code_dumper_extension_reg {
+ const struct sieve_code_dumper_extension *cdmpext;
+ const struct sieve_extension *ext;
+ void *context;
+};
+
+struct sieve_code_dumper {
+ pool_t pool;
+
+ /* Dump status */
+ struct sieve_operation oprtn;
+ sieve_size_t mark_address;
+ unsigned int mark_line;
+ unsigned int mark_last_line;
+ unsigned int indent;
+
+ /* Dump environment */
+ struct sieve_dumptime_env *dumpenv;
+
+ struct sieve_binary_debug_reader *dreader;
+
+ ARRAY(struct sieve_code_dumper_extension_reg) extensions;
+};
+
+struct sieve_code_dumper *sieve_code_dumper_create
+(struct sieve_dumptime_env *denv)
+{
+ pool_t pool;
+ struct sieve_code_dumper *cdumper;
+
+ pool = pool_alloconly_create("sieve_code_dumper", 4096);
+ cdumper = p_new(pool, struct sieve_code_dumper, 1);
+ cdumper->pool = pool;
+ cdumper->dumpenv = denv;
+
+ /* Setup storage for extension contexts */
+ p_array_init(&cdumper->extensions, pool,
+ sieve_extensions_get_count(denv->svinst));
+
+ return cdumper;
+}
+
+void sieve_code_dumper_free(struct sieve_code_dumper **_cdumper)
+{
+ struct sieve_code_dumper *cdumper = *_cdumper;
+ const struct sieve_code_dumper_extension_reg *eregs;
+ unsigned int count, i;
+
+ sieve_binary_debug_reader_deinit(&cdumper->dreader);
+
+ /* Signal registered extensions that the dumper is being destroyed */
+ eregs = array_get(&cdumper->extensions, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( eregs[i].cdmpext != NULL && eregs[i].cdmpext->free != NULL )
+ eregs[i].cdmpext->free(cdumper, eregs[i].context);
+ }
+
+ pool_unref(&cdumper->pool);
+ *_cdumper = NULL;
+}
+
+pool_t sieve_code_dumper_pool(struct sieve_code_dumper *cdumper)
+{
+ return cdumper->pool;
+}
+
+/* EXtension support */
+
+void sieve_dump_extension_register
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext,
+ const struct sieve_code_dumper_extension *cdmpext, void *context)
+{
+ struct sieve_code_dumper_extension_reg *reg;
+
+ if ( ext->id < 0 ) return;
+
+ reg = array_idx_get_space(&cdumper->extensions, (unsigned int) ext->id);
+ reg->cdmpext = cdmpext;
+ reg->ext = ext;
+ reg->context = context;
+}
+
+void sieve_dump_extension_set_context
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_code_dumper_extension_reg *reg;
+
+ if ( ext->id < 0 ) return;
+
+ reg = array_idx_get_space(&cdumper->extensions, (unsigned int) ext->id);
+ reg->context = context;
+}
+
+void *sieve_dump_extension_get_context
+(struct sieve_code_dumper *cdumper, const struct sieve_extension *ext)
+{
+ const struct sieve_code_dumper_extension_reg *reg;
+
+ if ( ext->id < 0 || ext->id >= (int) array_count(&cdumper->extensions) )
+ return NULL;
+
+ reg = array_idx(&cdumper->extensions, (unsigned int) ext->id);
+
+ return reg->context;
+}
+
+/* Dump functions */
+
+void sieve_code_dumpf
+(const struct sieve_dumptime_env *denv, const char *fmt, ...)
+{
+ struct sieve_code_dumper *cdumper = denv->cdumper;
+ unsigned tab = cdumper->indent;
+
+ string_t *outbuf = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_printfa(outbuf, "%08llx: ", (unsigned long long) cdumper->mark_address);
+
+ if ( cdumper->mark_line > 0 && (cdumper->indent == 0 ||
+ cdumper->mark_line != cdumper->mark_last_line) ) {
+ str_printfa(outbuf, "%4u: ", cdumper->mark_line);
+ cdumper->mark_last_line = cdumper->mark_line;
+ } else {
+ str_append(outbuf, " ");
+ }
+
+ while ( tab > 0 ) {
+ str_append(outbuf, " ");
+ tab--;
+ }
+
+ str_vprintfa(outbuf, fmt, args);
+ str_append_c(outbuf, '\n');
+ va_end(args);
+
+ o_stream_nsend(denv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+static inline void sieve_code_line_mark
+(const struct sieve_dumptime_env *denv, sieve_size_t location)
+{
+ if ( denv->cdumper->dreader != NULL ) {
+ denv->cdumper->mark_line = sieve_binary_debug_read_line
+ (denv->cdumper->dreader, location);
+ }
+}
+
+void sieve_code_mark(const struct sieve_dumptime_env *denv)
+{
+ denv->cdumper->mark_address = denv->offset;
+ sieve_code_line_mark(denv, denv->offset);
+}
+
+void sieve_code_mark_specific
+(const struct sieve_dumptime_env *denv, sieve_size_t location)
+{
+ denv->cdumper->mark_address = location;
+ sieve_code_line_mark(denv, location);
+}
+
+void sieve_code_descend(const struct sieve_dumptime_env *denv)
+{
+ denv->cdumper->indent++;
+}
+
+void sieve_code_ascend(const struct sieve_dumptime_env *denv)
+{
+ if ( denv->cdumper->indent > 0 )
+ denv->cdumper->indent--;
+}
+
+/* Code Dump */
+
+static bool sieve_code_dumper_print_operation
+(struct sieve_code_dumper *cdumper)
+{
+ struct sieve_dumptime_env *denv = cdumper->dumpenv;
+ struct sieve_operation *oprtn = &(cdumper->oprtn);
+ sieve_size_t *address = &(denv->offset);
+
+ /* Mark start address of operation */
+ cdumper->indent = 0;
+ cdumper->mark_address = *address;
+
+ sieve_code_line_mark(denv, *address);
+
+ /* Read operation */
+ if ( sieve_operation_read(denv->sblock, address, oprtn) ) {
+ const struct sieve_operation_def *opdef = oprtn->def;
+
+ if ( opdef->dump != NULL )
+ return opdef->dump(denv, address);
+ else if ( opdef->mnemonic != NULL )
+ sieve_code_dumpf(denv, "%s", opdef->mnemonic);
+ else
+ return FALSE;
+
+ return TRUE;
+ }
+
+ sieve_code_dumpf(denv, "Failed to read opcode.");
+ return FALSE;
+}
+
+static bool sieve_code_dumper_print_extension
+(struct sieve_code_dumper *cdumper)
+{
+ struct sieve_dumptime_env *denv = cdumper->dumpenv;
+ sieve_size_t *address = &(denv->offset);
+ struct sieve_binary_block *sblock = denv->sblock;
+ unsigned int code = 0, deferred;
+ const struct sieve_extension *ext;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_binary_read_extension
+ (sblock, address, &code, &ext) ||
+ !sieve_binary_read_byte
+ (sblock, address, &deferred) ) {
+ return FALSE;
+ }
+
+ if ( ext->def == NULL) {
+ sieve_code_dumpf(denv, "[undefined]");
+
+ } else {
+ sieve_code_dumpf(denv, "%s%s",
+ sieve_extension_name(ext),
+ (deferred > 0 ? " (deferred)" : ""));
+
+ if (ext->def->code_dump != NULL ) {
+ sieve_code_descend(denv);
+ if ( !ext->def->code_dump(ext, denv, address) )
+ return FALSE;
+ sieve_code_ascend(denv);
+ }
+ }
+ return TRUE;
+}
+
+void sieve_code_dumper_run(struct sieve_code_dumper *cdumper)
+{
+ struct sieve_dumptime_env *denv = cdumper->dumpenv;
+ struct sieve_binary *sbin = denv->sbin;
+ struct sieve_binary_block *sblock = denv->sblock;
+ unsigned int debug_block_id, ext_count;
+ bool success;
+ sieve_size_t *address;
+
+ denv->offset = 0;
+ denv->oprtn = &(cdumper->oprtn);
+ address = &(denv->offset);
+
+ /* Heading */
+ o_stream_nsend_str(denv->stream, "Address Line Code\n");
+
+ /* Load debug block */
+ sieve_code_mark(denv);
+
+ if ( sieve_binary_read_unsigned(sblock, address, &debug_block_id) ) {
+ struct sieve_binary_block *debug_block =
+ sieve_binary_block_get(sbin, debug_block_id);
+
+ if ( debug_block == NULL ) {
+ sieve_code_dumpf(denv, "Invalid id %d for debug block.", debug_block_id);
+ return;
+ } else {
+ /* Initialize debug reader */
+ cdumper->dreader = sieve_binary_debug_reader_init(debug_block);
+
+ /* Dump block id */
+ sieve_code_dumpf(denv, "DEBUG BLOCK: %d", debug_block_id);
+ }
+ } else {
+ sieve_code_dumpf(denv, "Binary code header is corrupt.");
+ return;
+ }
+
+ /* Load and dump extensions listed in code */
+ sieve_code_mark(denv);
+
+ success = TRUE;
+ if ( sieve_binary_read_unsigned(sblock, address, &ext_count) ) {
+ unsigned int i;
+
+ sieve_code_dumpf(denv, "EXTENSIONS [%d]:", ext_count);
+ sieve_code_descend(denv);
+
+ for ( i = 0; success && (i < ext_count); i++ ) {
+ T_BEGIN {
+ success = success &&
+ sieve_code_dumper_print_extension(cdumper);
+ } T_END;
+ }
+
+ sieve_code_ascend(denv);
+ } else
+ success = FALSE;
+
+ if ( !success ) {
+ sieve_code_dumpf(denv, "Binary code header is corrupt.");
+ return;
+ }
+
+ while ( *address < sieve_binary_block_get_size(sblock) ) {
+
+ T_BEGIN {
+ success = sieve_code_dumper_print_operation(cdumper);
+ } T_END;
+
+ if ( !success ) {
+ sieve_code_dumpf(denv, "Binary is corrupt.");
+ return;
+ }
+ }
+
+ /* Mark end of the binary */
+ cdumper->indent = 0;
+ cdumper->mark_address = sieve_binary_block_get_size(sblock);
+ sieve_code_dumpf(denv, "[End of code]");
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-code-dumper.h b/pigeonhole/src/lib-sieve/sieve-code-dumper.h
new file mode 100644
index 0000000..651bd11
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-code-dumper.h
@@ -0,0 +1,55 @@
+#ifndef SIEVE_CODE_DUMPER_H
+#define SIEVE_CODE_DUMPER_H
+
+#include "sieve-common.h"
+
+struct sieve_code_dumper;
+
+struct sieve_code_dumper *sieve_code_dumper_create
+ (struct sieve_dumptime_env *denv);
+void sieve_code_dumper_free
+ (struct sieve_code_dumper **_dumper);
+pool_t sieve_code_dumper_pool
+ (struct sieve_code_dumper *dumper);
+
+/*
+ * Extension support
+ */
+
+struct sieve_code_dumper_extension {
+ const struct sieve_extension_def *ext;
+
+ void (*free)(struct sieve_code_dumper *dumper, void *context);
+};
+
+void sieve_dump_extension_register
+(struct sieve_code_dumper *dumper, const struct sieve_extension *ext,
+ const struct sieve_code_dumper_extension *dump_ext, void *context);
+void sieve_dump_extension_set_context
+ (struct sieve_code_dumper *dumper, const struct sieve_extension *ext,
+ void *context);
+void *sieve_dump_extension_get_context
+ (struct sieve_code_dumper *dumper, const struct sieve_extension *ext);
+
+/* Dump functions */
+
+void sieve_code_dumpf
+ (const struct sieve_dumptime_env *denv, const char *fmt, ...)
+ ATTR_FORMAT(2, 3);
+
+void sieve_code_mark(const struct sieve_dumptime_env *denv);
+void sieve_code_mark_specific
+ (const struct sieve_dumptime_env *denv, sieve_size_t location);
+void sieve_code_descend(const struct sieve_dumptime_env *denv);
+void sieve_code_ascend(const struct sieve_dumptime_env *denv);
+
+/* Operations and operands */
+
+bool sieve_code_dumper_print_optional_operands
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+/* Code dump (debugging purposes) */
+
+void sieve_code_dumper_run(struct sieve_code_dumper *dumper);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-code.c b/pigeonhole/src/lib-sieve/sieve-code.c
new file mode 100644
index 0000000..82bd7f1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-code.c
@@ -0,0 +1,1169 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-extensions.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-code.h"
+
+#include <stdio.h>
+
+/*
+ * Code stringlist
+ */
+
+/* Forward declarations */
+
+static int sieve_code_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_code_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int sieve_code_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+
+/* Coded stringlist object */
+
+struct sieve_code_stringlist {
+ struct sieve_stringlist strlist;
+
+ sieve_size_t start_address;
+ sieve_size_t end_address;
+ sieve_size_t current_offset;
+ int length;
+ int index;
+};
+
+static struct sieve_stringlist *sieve_code_stringlist_create
+(const struct sieve_runtime_env *renv,
+ sieve_size_t start_address, unsigned int length, sieve_size_t end)
+{
+ struct sieve_code_stringlist *strlist;
+
+ if ( end > sieve_binary_block_get_size(renv->sblock) )
+ return NULL;
+
+ strlist = t_new(struct sieve_code_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = sieve_code_stringlist_next_item;
+ strlist->strlist.reset = sieve_code_stringlist_reset;
+ strlist->strlist.get_length = sieve_code_stringlist_get_length;
+ strlist->start_address = start_address;
+ strlist->current_offset = start_address;
+ strlist->end_address = end;
+ strlist->length = length;
+ strlist->index = 0;
+
+ return &strlist->strlist;
+}
+
+/* Stringlist implementation */
+
+static int sieve_code_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct sieve_code_stringlist *strlist =
+ (struct sieve_code_stringlist *) _strlist;
+ sieve_size_t address;
+ *str_r = NULL;
+ int ret;
+
+ /* Check for end of list */
+ if ( strlist->index >= strlist->length )
+ return 0;
+
+ /* Read next item */
+ address = strlist->current_offset;
+ if ( (ret=sieve_opr_string_read(_strlist->runenv, &address, NULL, str_r))
+ == SIEVE_EXEC_OK ) {
+ strlist->index++;
+ strlist->current_offset = address;
+ return 1;
+ }
+
+ _strlist->exec_status = ret;
+ return -1;
+}
+
+static void sieve_code_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_code_stringlist *strlist =
+ (struct sieve_code_stringlist *) _strlist;
+
+ strlist->current_offset = strlist->start_address;
+ strlist->index = 0;
+}
+
+static int sieve_code_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_code_stringlist *strlist =
+ (struct sieve_code_stringlist *) _strlist;
+
+ return strlist->length;
+}
+
+static bool sieve_code_stringlist_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ unsigned int length, sieve_size_t end, const char *field_name)
+{
+ unsigned int i;
+
+ if ( end > sieve_binary_block_get_size(denv->sblock) )
+ return FALSE;
+
+ if ( field_name != NULL )
+ sieve_code_dumpf(denv, "%s: STRLIST [%u] (end: %08llx)",
+ field_name, length, (unsigned long long) end);
+ else
+ sieve_code_dumpf(denv, "STRLIST [%u] (end: %08llx)",
+ length, (unsigned long long) end);
+
+ sieve_code_descend(denv);
+
+ for ( i = 0; i < length; i++ ) {
+ bool success = TRUE;
+
+ T_BEGIN {
+ success = sieve_opr_string_dump(denv, address, NULL);
+ } T_END;
+
+ if ( !success || *address > end )
+ return FALSE;
+ }
+
+ if ( *address != end ) return FALSE;
+
+ sieve_code_ascend(denv);
+
+ return TRUE;
+}
+
+/*
+ * Core operands
+ */
+
+extern const struct sieve_operand_def comparator_operand;
+extern const struct sieve_operand_def match_type_operand;
+extern const struct sieve_operand_def address_part_operand;
+
+const struct sieve_operand_def *sieve_operands[] = {
+ &omitted_operand, /* SIEVE_OPERAND_OPTIONAL */
+ &number_operand,
+ &string_operand,
+ &stringlist_operand,
+ &comparator_operand,
+ &match_type_operand,
+ &address_part_operand,
+ &catenated_string_operand
+};
+
+const unsigned int sieve_operand_count =
+ N_ELEMENTS(sieve_operands);
+
+/*
+ * Operand functions
+ */
+
+sieve_size_t sieve_operand_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_operand_def *opr_def)
+{
+ sieve_size_t address;
+
+ if ( ext != NULL ) {
+ address = sieve_binary_emit_extension
+ (sblock, ext, sieve_operand_count);
+
+ sieve_binary_emit_extension_object
+ (sblock, &opr_def->ext_def->operands, opr_def->code);
+
+ return address;
+ }
+
+ return sieve_binary_emit_byte(sblock, opr_def->code);
+}
+
+bool sieve_operand_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+ const char *field_name, struct sieve_operand *operand)
+{
+ unsigned int code = sieve_operand_count;
+
+ operand->address = *address;
+ operand->field_name = field_name;
+ operand->ext = NULL;
+ operand->def = NULL;
+
+ if ( !sieve_binary_read_extension(sblock, address, &code, &operand->ext) )
+ return FALSE;
+
+ if ( operand->ext == NULL ) {
+ if ( code < sieve_operand_count )
+ operand->def = sieve_operands[code];
+
+ return ( operand->def != NULL );
+ }
+
+ if ( operand->ext->def == NULL )
+ return FALSE;
+
+ operand->def = (const struct sieve_operand_def *)
+ sieve_binary_read_extension_object(sblock, address,
+ &operand->ext->def->operands);
+
+ return ( operand->def != NULL );
+}
+
+/*
+ * Optional operand
+ */
+
+int sieve_opr_optional_next
+(struct sieve_binary_block *sblock, sieve_size_t *address, signed int *opt_code)
+{
+ /* Start of optional operand block */
+ if ( *opt_code == 0 ) {
+ sieve_size_t tmp_addr = *address;
+ unsigned int op;
+
+ if ( !sieve_binary_read_byte(sblock, &tmp_addr, &op) ||
+ op != SIEVE_OPERAND_OPTIONAL )
+ return 0;
+
+ *address = tmp_addr;
+ }
+
+ /* Read optional operand code */
+ if ( !sieve_binary_read_code(sblock, address, opt_code) )
+ return -1;
+
+ /* Return 0 at end of list */
+ return ( *opt_code != 0 ? 1 : 0 );
+}
+
+/*
+ * Operand definitions
+ */
+
+/* Omitted */
+
+const struct sieve_operand_class omitted_class =
+ { "OMITTED" };
+
+const struct sieve_operand_def omitted_operand = {
+ .name = "@OMITTED",
+ .code = SIEVE_OPERAND_OPTIONAL,
+ .class = &omitted_class
+};
+
+/* Number */
+
+static bool opr_number_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_number_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, sieve_number_t *number_r);
+
+const struct sieve_opr_number_interface number_interface = {
+ opr_number_dump,
+ opr_number_read
+};
+
+const struct sieve_operand_class number_class =
+ { "number" };
+
+const struct sieve_operand_def number_operand = {
+ .name = "@number",
+ .code = SIEVE_OPERAND_NUMBER,
+ .class = &number_class,
+ .interface = &number_interface
+};
+
+/* String */
+
+static bool opr_string_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_string_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+
+const struct sieve_opr_string_interface string_interface ={
+ opr_string_dump,
+ opr_string_read
+};
+
+const struct sieve_operand_class string_class =
+ { "string" };
+
+const struct sieve_operand_def string_operand = {
+ .name = "@string",
+ .code = SIEVE_OPERAND_STRING,
+ .class = &string_class,
+ .interface = &string_interface
+};
+
+/* String List */
+
+static bool opr_stringlist_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_stringlist_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, struct sieve_stringlist **strlist_r);
+
+const struct sieve_opr_stringlist_interface stringlist_interface = {
+ opr_stringlist_dump,
+ opr_stringlist_read
+};
+
+const struct sieve_operand_class stringlist_class =
+ { "string-list" };
+
+const struct sieve_operand_def stringlist_operand = {
+ .name = "@string-list",
+ .code = SIEVE_OPERAND_STRING_LIST,
+ .class = &stringlist_class,
+ .interface = &stringlist_interface
+};
+
+/* Catenated String */
+
+static bool opr_catenated_string_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *operand,
+ sieve_size_t *address);
+static int opr_catenated_string_read
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *operand,
+ sieve_size_t *address, string_t **str);
+
+const struct sieve_opr_string_interface catenated_string_interface = {
+ opr_catenated_string_dump,
+ opr_catenated_string_read
+};
+
+const struct sieve_operand_def catenated_string_operand = {
+ .name = "@catenated-string",
+ .code = SIEVE_OPERAND_CATENATED_STRING,
+ .class = &string_class,
+ .interface = &catenated_string_interface
+};
+
+/*
+ * Operand implementations
+ */
+
+/* Omitted */
+
+void sieve_opr_omitted_emit(struct sieve_binary_block *sblock)
+{
+ (void) sieve_operand_emit(sblock, NULL, &omitted_operand);
+}
+
+/* Number */
+
+void sieve_opr_number_emit
+(struct sieve_binary_block *sblock, sieve_number_t number)
+{
+ (void) sieve_operand_emit(sblock, NULL, &number_operand);
+ (void) sieve_binary_emit_integer(sblock, number);
+}
+
+bool sieve_opr_number_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name)
+{
+ const struct sieve_opr_number_interface *intf;
+
+ oprnd->field_name = field_name;
+
+ if ( !sieve_operand_is_number(oprnd) )
+ return FALSE;
+
+ intf = (const struct sieve_opr_number_interface *) oprnd->def->interface;
+
+ if ( intf->dump == NULL )
+ return FALSE;
+
+ return intf->dump(denv, oprnd, address);
+}
+
+bool sieve_opr_number_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name)
+{
+ struct sieve_operand operand;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) )
+ return FALSE;
+
+ return sieve_opr_number_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_number_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name, sieve_number_t *number_r)
+{
+ const struct sieve_opr_number_interface *intf;
+
+ oprnd->field_name = field_name;
+
+ if ( !sieve_operand_is_number(oprnd) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "expected number operand but found %s", sieve_operand_name(oprnd));
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ intf = (const struct sieve_opr_number_interface *) oprnd->def->interface;
+
+ if ( intf->read == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "number operand not implemented");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return intf->read(renv, oprnd, address, number_r);
+}
+
+int sieve_opr_number_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, sieve_number_t *number_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0)
+ return ret;
+
+ return sieve_opr_number_read_data
+ (renv, &operand, address, field_name, number_r);
+}
+
+static bool opr_number_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ sieve_number_t number = 0;
+
+ if (sieve_binary_read_integer(denv->sblock, address, &number) ) {
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: NUM %llu", oprnd->field_name,
+ (unsigned long long) number);
+ else
+ sieve_code_dumpf(denv, "NUM %llu", (unsigned long long) number);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int opr_number_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, sieve_number_t *number_r)
+{
+ if ( !sieve_binary_read_integer(renv->sblock, address, number_r) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd, "invalid number operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/* String */
+
+void sieve_opr_string_emit(struct sieve_binary_block *sblock, string_t *str)
+{
+ (void) sieve_operand_emit(sblock, NULL, &string_operand);
+ (void) sieve_binary_emit_string(sblock, str);
+}
+
+bool sieve_opr_string_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name)
+{
+ const struct sieve_opr_string_interface *intf;
+
+ oprnd->field_name = field_name;
+
+ if ( !sieve_operand_is_string(oprnd) ) {
+ sieve_code_dumpf(denv, "ERROR: INVALID STRING OPERAND %s",
+ sieve_operand_name(oprnd));
+ return FALSE;
+ }
+
+ intf = (const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+ if ( intf->dump == NULL ) {
+ sieve_code_dumpf(denv, "ERROR: DUMP STRING OPERAND");
+ return FALSE;
+ }
+
+ return intf->dump(denv, oprnd, address);
+}
+
+bool sieve_opr_string_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name)
+{
+ struct sieve_operand operand;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+ sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+ return FALSE;
+ }
+
+ return sieve_opr_string_dump_data(denv, &operand, address, field_name);
+}
+
+bool sieve_opr_string_dump_ex
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name, const char *omitted_value)
+{
+ struct sieve_operand operand;
+
+ sieve_code_mark(denv);
+ if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+ sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
+ return FALSE;
+ }
+
+ if ( omitted_value != NULL && sieve_operand_is_omitted(&operand) ) {
+ if ( *omitted_value != '\0' )
+ sieve_code_dumpf(denv, "%s: %s", field_name, omitted_value);
+ return TRUE;
+ }
+
+ return sieve_opr_string_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_string_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name, string_t **str_r)
+{
+ const struct sieve_opr_string_interface *intf;
+
+ oprnd->field_name = field_name;
+
+ if ( !sieve_operand_is_string(oprnd) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "expected string operand but found %s", sieve_operand_name(oprnd));
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ intf = (const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+ if ( intf->read == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "string operand not implemented");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return intf->read(renv, oprnd, address, str_r);
+}
+
+int sieve_opr_string_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, string_t **str_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0 )
+ return ret;
+
+ return sieve_opr_string_read_data(renv, &operand, address, field_name, str_r);
+}
+
+int sieve_opr_string_read_ex
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, bool optional, string_t **str_r, bool *literal_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0 )
+ return ret;
+
+ if ( optional && sieve_operand_is_omitted(&operand) ) {
+ *str_r = NULL;
+ return 1;
+ }
+
+ if ( literal_r != NULL )
+ *literal_r = sieve_operand_is_string_literal(&operand);
+
+ return sieve_opr_string_read_data(renv, &operand, address, field_name, str_r);
+}
+
+static void _dump_string
+(const struct sieve_dumptime_env *denv, string_t *str,
+ const char *field_name)
+{
+ if ( str_len(str) > 80 ) {
+ if ( field_name != NULL )
+ sieve_code_dumpf(denv, "%s: STR[%ld] \"%s",
+ field_name, (long) str_len(str), str_sanitize(str_c(str), 80));
+ else
+ sieve_code_dumpf(denv, "STR[%ld] \"%s",
+ (long) str_len(str), str_sanitize(str_c(str), 80));
+ } else {
+ if ( field_name != NULL )
+ sieve_code_dumpf(denv, "%s: STR[%ld] \"%s\"",
+ field_name, (long) str_len(str), str_sanitize(str_c(str), 80));
+ else
+ sieve_code_dumpf(denv, "STR[%ld] \"%s\"",
+ (long) str_len(str), str_sanitize(str_c(str), 80));
+ }
+}
+
+bool opr_string_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ string_t *str;
+
+ if ( sieve_binary_read_string(denv->sblock, address, &str) ) {
+ _dump_string(denv, str, oprnd->field_name);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int opr_string_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r)
+{
+ if ( !sieve_binary_read_string(renv->sblock, address, str_r) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "invalid string operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/* String list */
+
+void sieve_opr_stringlist_emit_start
+(struct sieve_binary_block *sblock, unsigned int listlen, void **context)
+{
+ sieve_size_t *end_offset = t_new(sieve_size_t, 1);
+
+ /* Emit byte identifying the type of operand */
+ (void) sieve_operand_emit(sblock, NULL, &stringlist_operand);
+
+ /* Give the interpreter an easy way to skip over this string list */
+ *end_offset = sieve_binary_emit_offset(sblock, 0);
+ *context = (void *) end_offset;
+
+ /* Emit the length of the list */
+ (void) sieve_binary_emit_unsigned(sblock, listlen);
+}
+
+void sieve_opr_stringlist_emit_item
+(struct sieve_binary_block *sblock, void *context ATTR_UNUSED, string_t *item)
+{
+ (void) sieve_opr_string_emit(sblock, item);
+}
+
+void sieve_opr_stringlist_emit_end
+(struct sieve_binary_block *sblock, void *context)
+{
+ sieve_size_t *end_offset = (sieve_size_t *) context;
+
+ (void) sieve_binary_resolve_offset(sblock, *end_offset);
+}
+
+bool sieve_opr_stringlist_dump_data
+(const struct sieve_dumptime_env *denv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name)
+{
+ if ( oprnd == NULL || oprnd->def == NULL )
+ return FALSE;
+
+ oprnd->field_name = field_name;
+
+ if ( oprnd->def->class == &stringlist_class ) {
+ const struct sieve_opr_stringlist_interface *intf =
+ (const struct sieve_opr_stringlist_interface *) oprnd->def->interface;
+
+ if ( intf->dump == NULL )
+ return FALSE;
+
+ return intf->dump(denv, oprnd, address);
+ } else if ( oprnd->def->class == &string_class ) {
+ const struct sieve_opr_string_interface *intf =
+ (const struct sieve_opr_string_interface *) oprnd->def->interface;
+
+ if ( intf->dump == NULL )
+ return FALSE;
+
+ return intf->dump(denv, oprnd, address);
+ }
+
+ return FALSE;
+}
+
+bool sieve_opr_stringlist_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name)
+{
+ struct sieve_operand operand;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+ return FALSE;
+ }
+
+ return sieve_opr_stringlist_dump_data(denv, &operand, address, field_name);
+}
+
+bool sieve_opr_stringlist_dump_ex
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name, const char *omitted_value)
+{
+ struct sieve_operand operand;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_operand_read(denv->sblock, address, field_name, &operand) ) {
+ return FALSE;
+ }
+
+ if ( omitted_value != NULL && sieve_operand_is_omitted(&operand) ) {
+ if ( *omitted_value != '\0' )
+ sieve_code_dumpf(denv, "%s: %s", field_name, omitted_value);
+ return TRUE;
+ }
+
+ return sieve_opr_stringlist_dump_data(denv, &operand, address, field_name);
+}
+
+int sieve_opr_stringlist_read_data
+(const struct sieve_runtime_env *renv, struct sieve_operand *oprnd,
+ sieve_size_t *address, const char *field_name,
+ struct sieve_stringlist **strlist_r)
+{
+ if ( oprnd == NULL || oprnd->def == NULL )
+ return SIEVE_EXEC_FAILURE;
+
+ oprnd->field_name = field_name;
+
+ if ( oprnd->def->class == &stringlist_class ) {
+ const struct sieve_opr_stringlist_interface *intf =
+ (const struct sieve_opr_stringlist_interface *) oprnd->def->interface;
+ int ret;
+
+ if ( intf->read == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "stringlist operand not implemented");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if ( (ret=intf->read(renv, oprnd, address, strlist_r)) <= 0 )
+ return ret;
+
+ return SIEVE_EXEC_OK;
+ } else if ( oprnd->def->class == &string_class ) {
+ /* Special case, accept single string as string list as well. */
+ const struct sieve_opr_string_interface *intf =
+ (const struct sieve_opr_string_interface *) oprnd->def->interface;
+ int ret;
+
+ if ( intf->read == NULL ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "stringlist string operand not implemented");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ if ( strlist_r == NULL ) {
+ if ( (ret=intf->read(renv, oprnd, address, NULL)) <= 0 )
+ return ret;
+ } else {
+ string_t *stritem;
+ if ( (ret=intf->read(renv, oprnd, address, &stritem)) <= 0 )
+ return ret;
+
+ *strlist_r = sieve_single_stringlist_create
+ (renv, stritem, FALSE);
+ }
+ return SIEVE_EXEC_OK;
+ }
+
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "expected stringlist or string operand but found %s",
+ sieve_operand_name(oprnd));
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_opr_stringlist_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, struct sieve_stringlist **strlist_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0 )
+ return ret;
+
+ return sieve_opr_stringlist_read_data
+ (renv, &operand, address, field_name, strlist_r);
+}
+
+int sieve_opr_stringlist_read_ex
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, bool optional, struct sieve_stringlist **strlist_r)
+{
+ struct sieve_operand operand;
+ int ret;
+
+ if ( (ret=sieve_operand_runtime_read(renv, address, field_name, &operand))
+ <= 0 )
+ return ret;
+
+ if ( optional && sieve_operand_is_omitted(&operand) ) {
+ *strlist_r = NULL;
+ return 1;
+ }
+
+ return sieve_opr_stringlist_read_data
+ (renv, &operand, address, field_name, strlist_r);
+}
+
+static bool opr_stringlist_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ sieve_size_t pc = *address;
+ sieve_size_t end;
+ unsigned int length = 0;
+ sieve_offset_t end_offset;
+
+ if ( !sieve_binary_read_offset(denv->sblock, address, &end_offset) )
+ return FALSE;
+
+ end = pc + end_offset;
+
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &length) )
+ return FALSE;
+
+ return sieve_code_stringlist_dump
+ (denv, address, length, end, oprnd->field_name);
+}
+
+static int opr_stringlist_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, struct sieve_stringlist **strlist_r)
+{
+ sieve_size_t pc = *address;
+ sieve_size_t end;
+ unsigned int length = 0;
+ sieve_offset_t end_offset;
+
+ if ( !sieve_binary_read_offset(renv->sblock, address, &end_offset) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "stringlist corrupt: invalid end offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ end = pc + end_offset;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &length) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "stringlist corrupt: invalid length data");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( strlist_r != NULL )
+ *strlist_r = sieve_code_stringlist_create
+ (renv, *address, (unsigned int) length, end);
+
+ /* Skip over the string list for now */
+ *address = end;
+
+ return SIEVE_EXEC_OK;
+}
+
+/* Catenated String */
+
+void sieve_opr_catenated_string_emit
+(struct sieve_binary_block *sblock, unsigned int elements)
+{
+ (void) sieve_operand_emit(sblock, NULL, &catenated_string_operand);
+ (void) sieve_binary_emit_unsigned(sblock, elements);
+}
+
+static bool opr_catenated_string_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ unsigned int elements = 0;
+ unsigned int i;
+
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &elements) )
+ return FALSE;
+
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: CAT-STR [%ld]:",
+ oprnd->field_name, (long) elements);
+ else
+ sieve_code_dumpf(denv, "CAT-STR [%ld]:", (long) elements);
+
+ sieve_code_descend(denv);
+ for ( i = 0; i < (unsigned int) elements; i++ ) {
+ if ( !sieve_opr_string_dump(denv, address, NULL) )
+ return FALSE;
+ }
+ sieve_code_ascend(denv);
+
+ return TRUE;
+}
+
+static int opr_catenated_string_read
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str)
+{
+ unsigned int elements = 0;
+ unsigned int i;
+ int ret;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &elements) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "catenated string corrupt: invalid element count data");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Parameter str can be NULL if we are requested to only skip and not
+ * actually read the argument.
+ */
+ if ( str == NULL ) {
+ for ( i = 0; i < (unsigned int) elements; i++ ) {
+ if ( (ret=sieve_opr_string_read(renv, address, NULL, NULL)) <= 0 )
+ return ret;
+ }
+ } else {
+ string_t *strelm;
+ string_t **elm = &strelm;
+
+ *str = t_str_new(128);
+ for ( i = 0; i < (unsigned int) elements; i++ ) {
+
+ if ( (ret=sieve_opr_string_read(renv, address, NULL, elm)) <= 0 )
+ return ret;
+
+ if ( elm != NULL ) {
+ str_append_str(*str, strelm);
+
+ if ( str_len(*str) > SIEVE_MAX_STRING_LEN ) {
+ str_truncate(*str, SIEVE_MAX_STRING_LEN);
+ elm = NULL;
+ }
+ }
+ }
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Core operations
+ */
+
+/* Forward declarations */
+
+static bool opc_jmp_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+static int opc_jmp_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+static int opc_jmptrue_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+static int opc_jmpfalse_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Operation objects defined in this file */
+
+const struct sieve_operation_def sieve_jmp_operation = {
+ .mnemonic = "JMP",
+ .code = SIEVE_OPERATION_JMP,
+ .dump = opc_jmp_dump,
+ .execute = opc_jmp_execute
+};
+
+const struct sieve_operation_def sieve_jmptrue_operation = {
+ .mnemonic = "JMPTRUE",
+ .code = SIEVE_OPERATION_JMPTRUE,
+ .dump = opc_jmp_dump,
+ .execute = opc_jmptrue_execute
+};
+
+const struct sieve_operation_def sieve_jmpfalse_operation = {
+ .mnemonic = "JMPFALSE",
+ .code = SIEVE_OPERATION_JMPFALSE,
+ .dump = opc_jmp_dump,
+ .execute = opc_jmpfalse_execute
+};
+
+/* Operation objects defined in other files */
+
+extern const struct sieve_operation_def cmd_stop_operation;
+extern const struct sieve_operation_def cmd_keep_operation;
+extern const struct sieve_operation_def cmd_discard_operation;
+extern const struct sieve_operation_def cmd_redirect_operation;
+
+extern const struct sieve_operation_def tst_address_operation;
+extern const struct sieve_operation_def tst_header_operation;
+extern const struct sieve_operation_def tst_exists_operation;
+extern const struct sieve_operation_def tst_size_over_operation;
+extern const struct sieve_operation_def tst_size_under_operation;
+
+const struct sieve_operation_def *sieve_operations[] = {
+ NULL,
+
+ &sieve_jmp_operation,
+ &sieve_jmptrue_operation,
+ &sieve_jmpfalse_operation,
+
+ &cmd_stop_operation,
+ &cmd_keep_operation,
+ &cmd_discard_operation,
+ &cmd_redirect_operation,
+
+ &tst_address_operation,
+ &tst_header_operation,
+ &tst_exists_operation,
+ &tst_size_over_operation,
+ &tst_size_under_operation
+};
+
+const unsigned int sieve_operation_count =
+ N_ELEMENTS(sieve_operations);
+
+/*
+ * Operation functions
+ */
+
+sieve_size_t sieve_operation_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_operation_def *op_def)
+{
+ sieve_size_t address;
+
+ if ( ext != NULL ) {
+ i_assert( op_def->ext_def != NULL );
+ address = sieve_binary_emit_extension
+ (sblock, ext, sieve_operation_count);
+
+ sieve_binary_emit_extension_object
+ (sblock, &op_def->ext_def->operations, op_def->code);
+ return address;
+ }
+
+ i_assert( op_def->ext_def == NULL );
+ return sieve_binary_emit_byte(sblock, op_def->code);
+}
+
+bool sieve_operation_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct sieve_operation *oprtn)
+{
+ unsigned int code = sieve_operation_count;
+
+ oprtn->address = *address;
+ oprtn->def = NULL;
+ oprtn->ext = NULL;
+
+ if ( !sieve_binary_read_extension(sblock, address, &code, &oprtn->ext) )
+ return FALSE;
+
+ if ( oprtn->ext == NULL ) {
+ if ( code < sieve_operation_count ) {
+ oprtn->def = sieve_operations[code];
+ }
+
+ return ( oprtn->def != NULL );
+ }
+
+ oprtn->def = (const struct sieve_operation_def *)
+ sieve_binary_read_extension_object(sblock, address,
+ &oprtn->ext->def->operations);
+
+ return ( oprtn->def != NULL );
+}
+
+/*
+ * Jump operations
+ */
+
+/* Code dump */
+
+static bool opc_jmp_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ const struct sieve_operation *oprtn = denv->oprtn;
+ unsigned int pc = *address;
+ sieve_offset_t offset;
+
+ if ( sieve_binary_read_offset(denv->sblock, address, &offset) )
+ sieve_code_dumpf(denv, "%s %d [%08x]",
+ sieve_operation_mnemonic(oprtn), offset, pc + offset);
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Code execution */
+
+static int opc_jmp_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ return sieve_interpreter_program_jump(renv->interp, TRUE, FALSE);
+}
+
+static int opc_jmptrue_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ bool result = sieve_interpreter_get_test_result(renv->interp);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "jump if result is true");
+ sieve_runtime_trace_descend(renv);
+
+ return sieve_interpreter_program_jump(renv->interp, result, FALSE);
+}
+
+static int opc_jmpfalse_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ bool result = sieve_interpreter_get_test_result(renv->interp);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "jump if result is false");
+ sieve_runtime_trace_descend(renv);
+
+ return sieve_interpreter_program_jump(renv->interp, !result, FALSE);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-code.h b/pigeonhole/src/lib-sieve/sieve-code.h
new file mode 100644
index 0000000..00bf68b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-code.h
@@ -0,0 +1,351 @@
+#ifndef SIEVE_CODE_H
+#define SIEVE_CODE_H
+
+#include "lib.h"
+#include "buffer.h"
+#include "mempool.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-dump.h"
+
+/*
+ * Operand object
+ */
+
+struct sieve_operand_class {
+ const char *name;
+};
+
+struct sieve_operand_def {
+ const char *name;
+
+ const struct sieve_extension_def *ext_def;
+ unsigned int code;
+
+ const struct sieve_operand_class *class;
+ const void *interface;
+};
+
+struct sieve_operand {
+ const struct sieve_operand_def *def;
+ const struct sieve_extension *ext;
+ sieve_size_t address;
+ const char *field_name;
+};
+
+#define sieve_operand_name(opr) \
+ ( (opr)->def == NULL ? "(NULL)" : (opr)->def->name )
+#define sieve_operand_is(opr, definition) \
+ ( (opr)->def == &(definition) )
+
+sieve_size_t sieve_operand_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_operand_def *oprnd);
+bool sieve_operand_read
+ (struct sieve_binary_block *sblock, sieve_size_t *address,
+ const char *field_name, struct sieve_operand *oprnd);
+
+static inline int sieve_operand_runtime_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, struct sieve_operand *operand)
+{
+ if ( !sieve_operand_read(renv->sblock, address, field_name, operand) ) {
+ sieve_runtime_trace_operand_error(renv, operand, "invalid operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_opr_optional_next
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+ signed int *opt_code);
+
+static inline int sieve_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code)
+{
+ sieve_size_t pc = *address;
+ int ret;
+
+ if ( (ret=sieve_opr_optional_next(denv->sblock, address, opt_code)) <= 0 )
+ return ret;
+
+ sieve_code_mark_specific(denv, pc);
+ return ret;
+}
+
+static inline int sieve_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code)
+{
+ int ret;
+
+ if ( (ret=sieve_opr_optional_next(renv->sblock, address, opt_code)) < 0 )
+ sieve_runtime_trace_error(renv, "invalid optional operand code");
+
+ return ret;
+}
+
+/*
+ * Core operands
+ */
+
+/* Operand codes */
+
+enum sieve_core_operand {
+ SIEVE_OPERAND_OPTIONAL = 0x00,
+ SIEVE_OPERAND_NUMBER,
+ SIEVE_OPERAND_STRING,
+ SIEVE_OPERAND_STRING_LIST,
+ SIEVE_OPERAND_COMPARATOR,
+ SIEVE_OPERAND_MATCH_TYPE,
+ SIEVE_OPERAND_ADDRESS_PART,
+ SIEVE_OPERAND_CATENATED_STRING,
+
+ SIEVE_OPERAND_CUSTOM
+};
+
+/* Operand classes */
+
+extern const struct sieve_operand_class number_class;
+extern const struct sieve_operand_class string_class;
+extern const struct sieve_operand_class stringlist_class;
+
+/* Operand objects */
+
+extern const struct sieve_operand_def omitted_operand;
+extern const struct sieve_operand_def number_operand;
+extern const struct sieve_operand_def string_operand;
+extern const struct sieve_operand_def stringlist_operand;
+extern const struct sieve_operand_def catenated_string_operand;
+
+extern const struct sieve_operand_def *sieve_operands[];
+extern const unsigned int sieve_operand_count;
+
+/* Operand object interfaces */
+
+struct sieve_opr_number_interface {
+ bool (*dump)
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+ int (*read)
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, sieve_number_t *number_r);
+};
+
+struct sieve_opr_string_interface {
+ bool (*dump)
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+ int (*read)
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str_r);
+};
+
+struct sieve_opr_stringlist_interface {
+ bool (*dump)
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+ int (*read)
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, struct sieve_stringlist **strlist_r);
+};
+
+/*
+ * Core operand functions
+ */
+
+/* Omitted */
+
+void sieve_opr_omitted_emit(struct sieve_binary_block *sblock);
+
+static inline bool sieve_operand_is_omitted
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ operand->def == &omitted_operand );
+}
+
+/* Number */
+
+void sieve_opr_number_emit
+ (struct sieve_binary_block *sblock, sieve_number_t number);
+bool sieve_opr_number_dump_data
+ (const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name);
+bool sieve_opr_number_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name);
+int sieve_opr_number_read_data
+ (const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name, sieve_number_t *number_r);
+int sieve_opr_number_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, sieve_number_t *number_r);
+
+static inline bool sieve_operand_is_number
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ operand->def->class == &number_class );
+}
+
+/* String */
+
+void sieve_opr_string_emit
+ (struct sieve_binary_block *sblock, string_t *str);
+bool sieve_opr_string_dump_data
+ (const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name);
+bool sieve_opr_string_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name);
+bool sieve_opr_string_dump_ex
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name, const char *omitted_value);
+int sieve_opr_string_read_data
+ (const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name, string_t **str_r);
+int sieve_opr_string_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, string_t **str_r);
+int sieve_opr_string_read_ex
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, bool optional, string_t **str_r, bool *literal_r);
+
+static inline bool sieve_operand_is_string
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ operand->def->class == &string_class );
+}
+
+static inline bool sieve_operand_is_string_literal
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && sieve_operand_is(operand, string_operand) );
+}
+
+/* String list */
+
+void sieve_opr_stringlist_emit_start
+ (struct sieve_binary_block *sblock, unsigned int listlen, void **context);
+void sieve_opr_stringlist_emit_item
+ (struct sieve_binary_block *sblock, void *context ATTR_UNUSED,
+ string_t *item);
+void sieve_opr_stringlist_emit_end
+ (struct sieve_binary_block *sblock, void *context);
+bool sieve_opr_stringlist_dump_data
+ (const struct sieve_dumptime_env *denv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name);
+bool sieve_opr_stringlist_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name);
+bool sieve_opr_stringlist_dump_ex
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ const char *field_name, const char *omitted_value);
+int sieve_opr_stringlist_read_data
+ (const struct sieve_runtime_env *renv, struct sieve_operand *operand,
+ sieve_size_t *address, const char *field_name,
+ struct sieve_stringlist **strlist_r);
+int sieve_opr_stringlist_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, struct sieve_stringlist **strlist_r);
+int sieve_opr_stringlist_read_ex
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ const char *field_name, bool optional, struct sieve_stringlist **strlist_r);
+
+static inline bool sieve_operand_is_stringlist
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ (operand->def->class == &stringlist_class ||
+ operand->def->class == &string_class) );
+}
+
+/* Catenated string */
+
+void sieve_opr_catenated_string_emit
+ (struct sieve_binary_block *sblock, unsigned int elements);
+
+/*
+ * Operation object
+ */
+
+struct sieve_operation_def {
+ const char *mnemonic;
+
+ const struct sieve_extension_def *ext_def;
+ unsigned int code;
+
+ bool (*dump)
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+ int (*execute)
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+};
+
+struct sieve_operation {
+ const struct sieve_operation_def *def;
+ const struct sieve_extension *ext;
+
+ sieve_size_t address;
+};
+
+#define sieve_operation_is(oprtn, definition) \
+ ( (oprtn)->def == &(definition) )
+#define sieve_operation_mnemonic(oprtn) \
+ ( (oprtn)->def == NULL ? "(NULL)" : (oprtn)->def->mnemonic )
+
+sieve_size_t sieve_operation_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_operation_def *op_def);
+bool sieve_operation_read
+ (struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct sieve_operation *oprtn);
+const char *sieve_operation_read_string
+ (struct sieve_binary_block *sblock, sieve_size_t *address);
+
+/*
+ * Core operations
+ */
+
+/* Opcodes */
+
+enum sieve_operation_code {
+ SIEVE_OPERATION_INVALID,
+ SIEVE_OPERATION_JMP,
+ SIEVE_OPERATION_JMPTRUE,
+ SIEVE_OPERATION_JMPFALSE,
+
+ SIEVE_OPERATION_STOP,
+ SIEVE_OPERATION_KEEP,
+ SIEVE_OPERATION_DISCARD,
+ SIEVE_OPERATION_REDIRECT,
+
+ SIEVE_OPERATION_ADDRESS,
+ SIEVE_OPERATION_HEADER,
+ SIEVE_OPERATION_EXISTS,
+ SIEVE_OPERATION_SIZE_OVER,
+ SIEVE_OPERATION_SIZE_UNDER,
+
+ SIEVE_OPERATION_CUSTOM
+};
+
+/* Operation objects */
+
+extern const struct sieve_operation_def sieve_jmp_operation;
+extern const struct sieve_operation_def sieve_jmptrue_operation;
+extern const struct sieve_operation_def sieve_jmpfalse_operation;
+
+extern const struct sieve_operation_def *sieve_operations[];
+extern const unsigned int sieve_operations_count;
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-commands.c b/pigeonhole/src/lib-sieve/sieve-commands.c
new file mode 100644
index 0000000..324b66d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-commands.c
@@ -0,0 +1,403 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+
+/*
+ * Literal arguments
+ */
+
+/* Forward declarations */
+
+static bool arg_number_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+static bool arg_string_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+static bool arg_string_list_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *context);
+static bool arg_string_list_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+/* Argument objects */
+
+const struct sieve_argument_def number_argument = {
+ .identifier = "@number",
+ .generate = arg_number_generate
+};
+
+const struct sieve_argument_def string_argument = {
+ .identifier = "@string",
+ .generate = arg_string_generate
+};
+
+const struct sieve_argument_def string_list_argument = {
+ .identifier = "@string-list",
+ .validate = arg_string_list_validate,
+ .generate = arg_string_list_generate
+};
+
+/* Argument implementations */
+
+static bool arg_number_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ sieve_opr_number_emit(cgenv->sblock, sieve_ast_argument_number(arg));
+
+ return TRUE;
+}
+
+static bool arg_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ sieve_opr_string_emit(cgenv->sblock, sieve_ast_argument_str(arg));
+
+ return TRUE;
+}
+
+static bool arg_string_list_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *stritem;
+
+ stritem = sieve_ast_strlist_first(*arg);
+ while ( stritem != NULL ) {
+ if ( !sieve_validator_argument_activate(valdtr, cmd, stritem, FALSE) )
+ return FALSE;
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+
+ return TRUE;
+}
+
+static bool emit_string_list_operand
+(const struct sieve_codegen_env *cgenv, const struct sieve_ast_argument *strlist,
+ struct sieve_command *cmd)
+{
+ void *list_context;
+ struct sieve_ast_argument *stritem;
+
+ sieve_opr_stringlist_emit_start
+ (cgenv->sblock, sieve_ast_strlist_count(strlist), &list_context);
+
+ stritem = sieve_ast_strlist_first(strlist);
+ while ( stritem != NULL ) {
+ if ( !sieve_generate_argument(cgenv, stritem, cmd) )
+ return FALSE;
+
+ stritem = sieve_ast_strlist_next(stritem);
+ }
+
+ sieve_opr_stringlist_emit_end(cgenv->sblock, list_context);
+
+ return TRUE;
+}
+
+static bool arg_string_list_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ if ( sieve_ast_argument_type(arg) == SAAT_STRING ) {
+ return ( sieve_generate_argument(cgenv, arg, cmd) );
+
+ } else if ( sieve_ast_argument_type(arg) == SAAT_STRING_LIST ) {
+ bool result = TRUE;
+
+ if ( sieve_ast_strlist_count(arg) == 1 )
+ return ( sieve_generate_argument
+ (cgenv, sieve_ast_strlist_first(arg), cmd) );
+ else {
+ T_BEGIN {
+ result=emit_string_list_operand(cgenv, arg, cmd);
+ } T_END;
+ }
+
+ return result;
+ }
+
+ return FALSE;
+}
+
+/*
+ * Abstract arguments
+ *
+ * (Generated by processing and not by parsing the grammar)
+ */
+
+/* Catenated string */
+
+struct sieve_arg_catenated_string {
+ struct sieve_ast_arg_list *str_parts;
+};
+
+struct sieve_arg_catenated_string *sieve_arg_catenated_string_create
+(struct sieve_ast_argument *orig_arg)
+{
+ pool_t pool = sieve_ast_pool(orig_arg->ast);
+ struct sieve_ast_arg_list *arglist;
+ struct sieve_arg_catenated_string *catstr;
+
+ arglist = sieve_ast_arg_list_create(pool);
+
+ catstr = p_new(pool, struct sieve_arg_catenated_string, 1);
+ catstr->str_parts = arglist;
+ (orig_arg)->argument->data = (void *) catstr;
+
+ return catstr;
+}
+
+void sieve_arg_catenated_string_add_element
+(struct sieve_arg_catenated_string *catstr,
+ struct sieve_ast_argument *element)
+{
+ sieve_ast_arg_list_add(catstr->str_parts, element);
+}
+
+#define _cat_string_first(catstr) __AST_LIST_FIRST((catstr)->str_parts)
+#define _cat_string_count(catstr) __AST_LIST_COUNT((catstr)->str_parts)
+#define _cat_string_next(item) __AST_LIST_NEXT(item)
+
+bool sieve_arg_catenated_string_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_arg_catenated_string *catstr =
+ (struct sieve_arg_catenated_string *) arg->argument->data;
+ struct sieve_ast_argument *strpart;
+
+ if ( _cat_string_count(catstr) == 1 )
+ sieve_generate_argument(cgenv, _cat_string_first(catstr), cmd);
+ else {
+ sieve_opr_catenated_string_emit(cgenv->sblock, _cat_string_count(catstr));
+
+ strpart = _cat_string_first(catstr);
+ while ( strpart != NULL ) {
+ if ( !sieve_generate_argument(cgenv, strpart, cmd) )
+ return FALSE;
+
+ strpart = _cat_string_next(strpart);
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Argument creation
+ */
+
+struct sieve_argument *sieve_argument_create
+(struct sieve_ast *ast, const struct sieve_argument_def *def,
+ const struct sieve_extension *ext, int id_code)
+{
+ struct sieve_argument *arg;
+ pool_t pool;
+
+ pool = sieve_ast_pool(ast);
+ arg = p_new(pool, struct sieve_argument, 1);
+ arg->def = def;
+ arg->ext = ext;
+ arg->id_code = id_code;
+
+ return arg;
+}
+
+/*
+ * Core tests and commands
+ */
+
+const struct sieve_command_def *sieve_core_tests[] = {
+ &tst_false, &tst_true,
+ &tst_not, &tst_anyof, &tst_allof,
+ &tst_address, &tst_header, &tst_exists, &tst_size
+};
+
+const unsigned int sieve_core_tests_count = N_ELEMENTS(sieve_core_tests);
+
+const struct sieve_command_def *sieve_core_commands[] = {
+ &cmd_require,
+ &cmd_stop, &cmd_if, &cmd_elsif, &cmd_else,
+ &cmd_keep, &cmd_discard, &cmd_redirect
+};
+
+const unsigned int sieve_core_commands_count = N_ELEMENTS(sieve_core_commands);
+
+/*
+ * Command context
+ */
+
+struct sieve_command *sieve_command_prev
+(struct sieve_command *cmd)
+{
+ struct sieve_ast_node *node = sieve_ast_node_prev(cmd->ast_node);
+
+ if ( node != NULL ) {
+ return node->command;
+ }
+
+ return NULL;
+}
+
+struct sieve_command *sieve_command_parent
+(struct sieve_command *cmd)
+{
+ struct sieve_ast_node *node = sieve_ast_node_parent(cmd->ast_node);
+
+ return ( node != NULL ? node->command : NULL );
+}
+
+struct sieve_command *sieve_command_create
+(struct sieve_ast_node *cmd_node, const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def,
+ struct sieve_command_registration *cmd_reg)
+{
+ struct sieve_command *cmd;
+
+ cmd = p_new(sieve_ast_node_pool(cmd_node), struct sieve_command, 1);
+
+ cmd->ast_node = cmd_node;
+ cmd->def = cmd_def;
+ cmd->ext = ext;
+ cmd->reg = cmd_reg;
+
+ cmd->block_exit_command = NULL;
+
+ return cmd;
+}
+
+const char *sieve_command_def_type_name
+(const struct sieve_command_def *cmd_def)
+{
+ switch ( cmd_def->type ) {
+ case SCT_NONE: return "command of unspecified type (bug)";
+ case SCT_TEST: return "test";
+ case SCT_COMMAND: return "command";
+ case SCT_HYBRID: return "command or test";
+ default:
+ break;
+ }
+ return "??COMMAND-TYPE??";
+}
+
+const char *sieve_command_type_name
+ (const struct sieve_command *cmd)
+{
+ switch ( cmd->def->type ) {
+ case SCT_NONE: return "command of unspecified type (bug)";
+ case SCT_TEST: return "test";
+ case SCT_COMMAND: return "command";
+ case SCT_HYBRID:
+ if ( cmd->ast_node->type == SAT_TEST )
+ return "test";
+ return "command";
+ default:
+ break;
+ }
+ return "??COMMAND-TYPE??";
+}
+
+struct sieve_ast_argument *sieve_command_add_dynamic_tag
+(struct sieve_command *cmd, const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag, int id_code)
+{
+ struct sieve_ast_argument *arg;
+
+ if ( cmd->first_positional != NULL )
+ arg = sieve_ast_argument_tag_insert
+ (cmd->first_positional, tag->identifier, cmd->ast_node->source_line);
+ else
+ arg = sieve_ast_argument_tag_create
+ (cmd->ast_node, tag->identifier, cmd->ast_node->source_line);
+
+ arg->argument = sieve_argument_create(cmd->ast_node->ast, tag, ext, id_code);
+
+ return arg;
+}
+
+struct sieve_ast_argument *sieve_command_find_argument
+(struct sieve_command *cmd, const struct sieve_argument_def *arg_def)
+{
+ struct sieve_ast_argument *arg = sieve_ast_argument_first(cmd->ast_node);
+
+ /* Visit tagged and optional arguments */
+ while ( arg != NULL ) {
+ if ( arg->argument != NULL && arg->argument->def == arg_def )
+ return arg;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ return arg;
+}
+
+/* Use this function with caution. The command commits to exiting the block.
+ * When it for some reason does not, the interpretation will break later on,
+ * because exiting jumps are not generated when they would otherwise be
+ * necessary.
+ */
+void sieve_command_exit_block_unconditionally
+ (struct sieve_command *cmd)
+{
+ struct sieve_command *parent = sieve_command_parent(cmd);
+
+ /* Only the first unconditional exit is of importance */
+ if ( parent != NULL && parent->block_exit_command == NULL )
+ parent->block_exit_command = cmd;
+}
+
+bool sieve_command_block_exits_unconditionally
+ (struct sieve_command *cmd)
+{
+ return ( cmd->block_exit_command != NULL );
+}
+
+/*
+ * Command utility functions
+ */
+
+/* NOTE: this may be moved */
+
+static int _verify_header_name_item
+(void *context, struct sieve_ast_argument *header)
+{
+ struct sieve_validator *valdtr = (struct sieve_validator *) context;
+ string_t *name = sieve_ast_argument_str(header);
+
+ if ( sieve_argument_is_string_literal(header) &&
+ !rfc2822_header_field_name_verify(str_c(name), str_len(name)) ) {
+ sieve_argument_validate_warning
+ (valdtr, header, "specified header field name '%s' is invalid",
+ str_sanitize(str_c(name), 80));
+
+ return 0;
+ }
+
+ return 1;
+}
+
+bool sieve_command_verify_headers_argument
+(struct sieve_validator *valdtr, struct sieve_ast_argument *headers)
+{
+ return ( sieve_ast_stringlist_map
+ (&headers, (void *) valdtr, _verify_header_name_item) >= 0 );
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-commands.h b/pigeonhole/src/lib-sieve/sieve-commands.h
new file mode 100644
index 0000000..f9b83e3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-commands.h
@@ -0,0 +1,286 @@
+#ifndef SIEVE_COMMANDS_H
+#define SIEVE_COMMANDS_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+
+/*
+ * Argument definition
+ */
+
+enum sieve_argument_flag {
+ /* More than one of this (type of) tagged argument is allowed */
+ SIEVE_ARGUMENT_FLAG_MULTIPLE = (1 << 0)
+};
+
+struct sieve_argument_def {
+ const char *identifier;
+ enum sieve_argument_flag flags;
+
+ bool (*is_instance_of)
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data);
+
+ bool (*validate)
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+ bool (*validate_context)
+ (struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+ bool (*validate_persistent)
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext);
+
+ bool (*generate)
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+};
+
+/*
+ * Argument instance
+ */
+
+struct sieve_argument {
+ const struct sieve_argument_def *def;
+ const struct sieve_extension *ext;
+ int id_code;
+
+ /* Context data */
+ void *data;
+};
+
+#define sieve_argument_is(ast_arg, definition) \
+ ( (ast_arg)->argument->def == &(definition) )
+#define sieve_argument_ext(ast_arg) \
+ ( (ast_arg)->argument->ext )
+#define sieve_argument_identifier(ast_arg) \
+ ( (ast_arg)->argument->def->identifier )
+
+/* Utility macros */
+
+#define sieve_argument_is_string_literal(arg) \
+ ( (arg)->argument->def == &string_argument )
+
+/* Error handling */
+
+#define sieve_argument_validate_error(validator, arg_node, ...) \
+ sieve_validator_error(validator, \
+ ((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+ __VA_ARGS__)
+#define sieve_argument_validate_warning(validator, arg_node, ...) \
+ sieve_validator_warning(validator, \
+ ((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+ __VA_ARGS__)
+
+#define sieve_argument_generate_error(gentr, arg_node, ...) \
+ sieve_generator_error(gentr, \
+ ((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+ __VA_ARGS__)
+#define sieve_argument_generate_warning(gentr, arg_node, ...) \
+ sieve_generator_warning(gentr, \
+ ((arg_node) == NULL ? 0 : (arg_node)->source_line), \
+ __VA_ARGS__)
+
+/* Argument API */
+
+struct sieve_argument *sieve_argument_create
+ (struct sieve_ast *ast, const struct sieve_argument_def *def,
+ const struct sieve_extension *ext, int id_code);
+
+/* Literal arguments */
+
+extern const struct sieve_argument_def number_argument;
+extern const struct sieve_argument_def string_argument;
+extern const struct sieve_argument_def string_list_argument;
+
+/* Catenated string argument */
+
+bool sieve_arg_catenated_string_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+struct sieve_arg_catenated_string;
+
+struct sieve_arg_catenated_string *sieve_arg_catenated_string_create
+ (struct sieve_ast_argument *orig_arg);
+void sieve_arg_catenated_string_add_element
+ (struct sieve_arg_catenated_string *strdata,
+ struct sieve_ast_argument *element);
+
+/*
+ * Command definition
+ */
+
+enum sieve_command_type {
+ SCT_NONE,
+ SCT_COMMAND,
+ SCT_TEST,
+ SCT_HYBRID
+};
+
+struct sieve_command_def {
+ const char *identifier;
+ enum sieve_command_type type;
+
+ /* High-level command syntax */
+ int positional_args;
+ int subtests;
+ bool block_allowed;
+ bool block_required;
+
+ bool (*registered)
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+ bool (*pre_validate)
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+ bool (*validate)
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+ bool (*validate_const)
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ int *const_current, int const_next);
+ bool (*generate)
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+ bool (*control_generate)
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+ struct sieve_jumplist *jumps, bool jump_true);
+};
+
+/*
+ * Command instance
+ */
+
+struct sieve_command {
+ const struct sieve_command_def *def;
+ const struct sieve_extension *ext;
+
+ /* The registration of this command in the validator (sieve-validator.h) */
+ struct sieve_command_registration *reg;
+
+ /* The ast node of this command */
+ struct sieve_ast_node *ast_node;
+
+ /* First positional argument, found during argument validation */
+ struct sieve_ast_argument *first_positional;
+
+ /* The child ast node that unconditionally exits this command's block */
+ struct sieve_command *block_exit_command;
+
+ /* Context data*/
+ void *data;
+};
+
+#define sieve_command_is(cmd, definition) \
+ ( (cmd)->def == &(definition) )
+#define sieve_command_identifier(cmd) \
+ ( (cmd)->def->identifier )
+
+#define sieve_commands_equal(cmd1, cmd2) \
+ ( (cmd1) != NULL && (cmd2) != NULL && (cmd1)->def == (cmd2)->def )
+
+/* Context API */
+
+struct sieve_command *sieve_command_create
+ (struct sieve_ast_node *cmd_node, const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def,
+ struct sieve_command_registration *cmd_reg);
+
+const char *sieve_command_def_type_name
+ (const struct sieve_command_def *cmd_def);
+const char *sieve_command_type_name
+ (const struct sieve_command *cmd);
+
+struct sieve_command *sieve_command_prev
+ (struct sieve_command *cmd);
+struct sieve_command *sieve_command_parent
+ (struct sieve_command *cmd);
+
+struct sieve_ast_argument *sieve_command_add_dynamic_tag
+ (struct sieve_command *cmd, const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag, int id_code);
+struct sieve_ast_argument *sieve_command_find_argument
+ (struct sieve_command *cmd, const struct sieve_argument_def *argument);
+
+void sieve_command_exit_block_unconditionally
+ (struct sieve_command *cmd);
+bool sieve_command_block_exits_unconditionally
+ (struct sieve_command *cmd);
+
+/* Error handling */
+
+#define sieve_command_validate_error(validator, context, ...) \
+ sieve_validator_error(validator, \
+ ((context) == NULL ? 0 : (context)->ast_node->source_line), \
+ __VA_ARGS__)
+#define sieve_command_validate_warning(validator, context, ...) \
+ sieve_validator_warning(validator, \
+ ((context) == NULL ? 0 : (context)->ast_node->source_line), \
+ __VA_ARGS__)
+
+#define sieve_command_generate_error(gentr, context, ...) \
+ sieve_generator_error(gentr, \
+ ((context) == NULL ? 0 : (context)->ast_node->source_line), \
+ __VA_ARGS__)
+#define sieve_command_generate_warning(gentr, context, ...) \
+ sieve_generator_warning(gentr, \
+ ((context) == NULL ? 0 : (context)->ast_node->source_line), \
+ __VA_ARGS__)
+
+/* Utility macros */
+
+#define sieve_command_pool(context) \
+ sieve_ast_node_pool((context)->ast_node)
+
+#define sieve_command_source_line(context) \
+ (context)->ast_node->source_line
+
+#define sieve_command_first_argument(context) \
+ sieve_ast_argument_first((context)->ast_node)
+
+#define sieve_command_is_toplevel(context) \
+ ( sieve_ast_node_type(sieve_ast_node_parent((context)->ast_node)) == SAT_ROOT )
+#define sieve_command_is_first(context) \
+ ( sieve_ast_node_prev((context)->ast_node) == NULL )
+
+/*
+ * Core commands
+ */
+
+extern const struct sieve_command_def cmd_require;
+extern const struct sieve_command_def cmd_stop;
+extern const struct sieve_command_def cmd_if;
+extern const struct sieve_command_def cmd_elsif;
+extern const struct sieve_command_def cmd_else;
+extern const struct sieve_command_def cmd_redirect;
+extern const struct sieve_command_def cmd_keep;
+extern const struct sieve_command_def cmd_discard;
+
+extern const struct sieve_command_def *sieve_core_commands[];
+extern const unsigned int sieve_core_commands_count;
+
+/*
+ * Core tests
+ */
+
+extern const struct sieve_command_def tst_true;
+extern const struct sieve_command_def tst_false;
+extern const struct sieve_command_def tst_not;
+extern const struct sieve_command_def tst_anyof;
+extern const struct sieve_command_def tst_allof;
+extern const struct sieve_command_def tst_address;
+extern const struct sieve_command_def tst_header;
+extern const struct sieve_command_def tst_exists;
+extern const struct sieve_command_def tst_size;
+
+extern const struct sieve_command_def *sieve_core_tests[];
+extern const unsigned int sieve_core_tests_count;
+
+/*
+ * Command utility functions
+ */
+
+bool sieve_command_verify_headers_argument
+(struct sieve_validator *valdtr, struct sieve_ast_argument *headers);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-common.h b/pigeonhole/src/lib-sieve/sieve-common.h
new file mode 100644
index 0000000..e79fb4d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-common.h
@@ -0,0 +1,240 @@
+#ifndef SIEVE_COMMON_H
+#define SIEVE_COMMON_H
+
+#include "lib.h"
+
+#include "sieve.h"
+
+#include <sys/types.h>
+
+/*
+ * Types
+ */
+
+typedef size_t sieve_size_t;
+typedef uint32_t sieve_offset_t;
+typedef uint64_t sieve_number_t;
+
+#define SIEVE_MAX_NUMBER ((sieve_number_t)-1)
+#define SIEVE_PRI_NUMBER PRIu64
+
+/*
+ * Forward declarations
+ */
+
+/* sieve-error.h */
+struct sieve_error_handler;
+
+/* sieve-ast.h */
+enum sieve_ast_argument_type;
+
+struct sieve_ast;
+struct sieve_ast_node;
+struct sieve_ast_argument;
+
+/* sieve-commands.h */
+struct sieve_argument;
+struct sieve_argument_def;
+struct sieve_command;
+struct sieve_command_def;
+struct sieve_command_context;
+struct sieve_command_registration;
+
+/* sieve-stringlist.h */
+struct sieve_stringlist;
+
+/* sieve-code.h */
+struct sieve_operation_extension;
+
+/* sieve-lexer.h */
+struct sieve_lexer;
+
+/* sieve-parser.h */
+struct sieve_parser;
+
+/* sieve-validator.h */
+struct sieve_validator;
+
+/* sieve-generator.h */
+struct sieve_jumplist;
+struct sieve_generator;
+struct sieve_codegen_env;
+
+/* sieve-runtime.h */
+struct sieve_runtime_env;
+
+/* sieve-interpreter.h */
+struct sieve_interpreter;
+
+/* sieve-dump.h */
+struct sieve_dumptime_env;
+
+/* sieve-binary-dumper.h */
+struct sieve_binary_dumper;
+
+/* sieve-code-dumper.h */
+struct sieve_code_dumper;
+
+/* sieve-extension.h */
+struct sieve_extension;
+struct sieve_extension_def;
+struct sieve_extension_objects;
+
+/* sieve-code.h */
+struct sieve_operand;
+struct sieve_operand_def;
+struct sieve_operand_class;
+struct sieve_operation;
+struct sieve_coded_stringlist;
+
+/* sieve-binary.h */
+struct sieve_binary;
+struct sieve_binary_block;
+struct sieve_binary_debug_writer;
+struct sieve_binary_debug_reader;
+
+/* sieve-execute.h */
+struct sieve_execute;
+
+/* sieve-objects.h */
+struct sieve_object_def;
+struct sieve_object;
+
+/* sieve-comparator.h */
+struct sieve_comparator;
+
+/* sieve-match-types.h */
+struct sieve_match_type;
+
+/* sieve-match.h */
+struct sieve_match_context;
+
+/* sieve-address.h */
+struct sieve_address_list;
+
+/* sieve-address-parts.h */
+struct sieve_address_part_def;
+struct sieve_address_part;
+
+/* sieve-result.h */
+struct sieve_result;
+struct sieve_side_effects_list;
+struct sieve_result_print_env;
+
+/* sieve-actions.h */
+struct sieve_action_exec_env;
+struct sieve_action;
+struct sieve_action_def;
+struct sieve_side_effect;
+struct sieve_side_effect_def;
+
+/* sieve-script.h */
+struct sieve_script;
+struct sieve_script_sequence;
+
+/* sieve-storage.h */
+struct sieve_storage_class_registry;
+struct sieve_storage;
+
+/* sieve-message.h */
+struct sieve_message_context;
+struct sieve_message_override;
+struct sieve_message_override_def;
+
+/* sieve-plugins.h */
+struct sieve_plugin;
+
+/* sieve.c */
+struct sieve_ast *sieve_parse
+ (struct sieve_script *script, struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r);
+bool sieve_validate
+ (struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r);
+
+/*
+ * Parent category
+ */
+
+extern struct event_category event_category_sieve;
+
+/*
+ * Sieve engine instance
+ */
+
+#include "sieve-address-source.h"
+
+struct sieve_instance {
+ /* Main engine pool */
+ pool_t pool;
+
+ /* System environment */
+ const char *hostname;
+ const char *domainname;
+ const char *base_dir;
+ const char *temp_dir;
+
+ /* User environment */
+ const char *username;
+ const char *home_dir;
+
+ /* Flags */
+ enum sieve_flag flags;
+
+ /* Callbacks */
+ const struct sieve_callbacks *callbacks;
+ void *context;
+
+ /* Logging, events, and debug */
+ struct event *event;
+ bool debug;
+
+ /* Extension registry */
+ struct sieve_extension_registry *ext_reg;
+
+ /* Storage class registry */
+ struct sieve_storage_class_registry *storage_reg;
+
+ /* Plugin modules */
+ struct sieve_plugin *plugins;
+ enum sieve_env_location env_location;
+ enum sieve_delivery_phase delivery_phase;
+
+ /* Settings */
+ size_t max_script_size;
+ unsigned int max_actions;
+ unsigned int max_redirects;
+ unsigned int max_cpu_time_secs;
+ unsigned int resource_usage_timeout_secs;
+ const struct smtp_address *user_email, *user_email_implicit;
+ struct sieve_address_source redirect_from;
+ unsigned int redirect_duplicate_period;
+};
+
+/*
+ * Script trace log
+ */
+
+void sieve_trace_log_write_line
+ (struct sieve_trace_log *trace_log, const string_t *line)
+ ATTR_NULL(2);
+
+/*
+ * User e-mail address
+ */
+
+const struct smtp_address *sieve_get_user_email
+ (struct sieve_instance *svinst);
+
+/*
+ * Postmaster address
+ */
+
+const struct message_address *
+sieve_get_postmaster(const struct sieve_script_env *senv);
+const struct smtp_address *
+sieve_get_postmaster_smtp(const struct sieve_script_env *senv);
+const char *
+sieve_get_postmaster_address(const struct sieve_script_env *senv);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-comparators.c b/pigeonhole/src/lib-sieve/sieve-comparators.c
new file mode 100644
index 0000000..56c33ab
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-comparators.c
@@ -0,0 +1,260 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-comparators.h"
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Core comparators
+ */
+
+const struct sieve_comparator_def *sieve_core_comparators[] = {
+ &i_octet_comparator, &i_ascii_casemap_comparator
+};
+
+const unsigned int sieve_core_comparators_count =
+ N_ELEMENTS(sieve_core_comparators);
+
+/*
+ * Comparator 'extension'
+ */
+
+static bool cmp_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def comparator_extension = {
+ .name = "@comparators",
+ .validator_load = cmp_validator_load
+};
+
+/*
+ * Validator context:
+ * name-based comparator registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *mcht_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ mcht_ext = sieve_get_comparator_extension(svinst);
+ return sieve_validator_object_registry_get(valdtr, mcht_ext);
+}
+
+void sieve_comparator_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_comparator_def *cmp)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ sieve_validator_object_registry_add(regs, ext, &cmp->obj_def);
+}
+
+static struct sieve_comparator *sieve_comparator_create
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+ struct sieve_object object;
+ struct sieve_comparator *cmp;
+
+ if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+ return NULL;
+
+ cmp = p_new(sieve_command_pool(cmd), struct sieve_comparator, 1);
+ cmp->object = object;
+ cmp->def = (const struct sieve_comparator_def *) object.def;
+
+ return cmp;
+}
+
+bool cmp_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ struct sieve_validator_object_registry *regs =
+ sieve_validator_object_registry_init(valdtr, ext);
+ unsigned int i;
+
+ /* Register core comparators */
+ for ( i = 0; i < sieve_core_comparators_count; i++ ) {
+ sieve_validator_object_registry_add
+ (regs, NULL, &(sieve_core_comparators[i]->obj_def));
+ }
+
+ return TRUE;
+}
+
+/*
+ * Comparator tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_comparator_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_comparator_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def comparator_tag = {
+ .identifier = "comparator",
+ .validate = tag_comparator_validate,
+ .generate = tag_comparator_generate
+};
+
+/* Argument implementation */
+
+static bool tag_comparator_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ const struct sieve_comparator *cmp;
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* Check syntax:
+ * ":comparator" <comparator-name: string>
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+ return FALSE;
+ }
+
+ /* FIXME: We can currently only handle string literal argument, so
+ * variables are not allowed.
+ */
+ if ( !sieve_argument_is_string_literal(*arg) ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "this Sieve implementation currently only supports "
+ "a literal string argument for the :comparator tag");
+ return FALSE;
+ }
+
+ /* Get comparator from registry */
+ cmp = sieve_comparator_create(valdtr, cmd, sieve_ast_argument_strc(*arg));
+
+ if ( cmp == NULL ) {
+ sieve_argument_validate_error(valdtr, *arg,
+ "unknown comparator '%s'",
+ str_sanitize(sieve_ast_argument_strc(*arg),80));
+
+ return FALSE;
+ }
+
+ /* String argument not needed during code generation, so detach it from
+ * argument list
+ */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Store comparator in context */
+ tag->argument->data = (void *) cmp;
+
+ return TRUE;
+}
+
+static bool tag_comparator_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ const struct sieve_comparator *cmp =
+ (const struct sieve_comparator *) arg->argument->data;
+
+ sieve_opr_comparator_emit(cgenv->sblock, cmp);
+
+ return TRUE;
+}
+
+/* Functions to enable and evaluate comparator tag for commands */
+
+void sieve_comparators_link_tag
+(struct sieve_validator *valdtr, struct sieve_command_registration *cmd_reg,
+ int id_code)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *mcht_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ mcht_ext = sieve_get_comparator_extension(svinst);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, mcht_ext, &comparator_tag, id_code);
+}
+
+bool sieve_comparator_tag_is
+(struct sieve_ast_argument *tag, const struct sieve_comparator_def *cmp_def)
+{
+ const struct sieve_comparator *cmp;
+
+ if ( !sieve_argument_is(tag, comparator_tag) )
+ return FALSE;
+
+ cmp = (const struct sieve_comparator *) tag->argument->data;
+
+ return ( cmp->def == cmp_def );
+}
+
+const struct sieve_comparator *sieve_comparator_tag_get
+(struct sieve_ast_argument *tag)
+{
+ if ( !sieve_argument_is(tag, comparator_tag) )
+ return NULL;
+
+
+ return (const struct sieve_comparator *) tag->argument->data;
+}
+
+/*
+ * Comparator coding
+ */
+
+const struct sieve_operand_class sieve_comparator_operand_class =
+ { "comparator" };
+
+static const struct sieve_extension_objects core_comparators =
+ SIEVE_EXT_DEFINE_COMPARATORS(sieve_core_comparators);
+
+const struct sieve_operand_def comparator_operand = {
+ .name = "comparator",
+ .code = SIEVE_OPERAND_COMPARATOR,
+ .class = &sieve_comparator_operand_class,
+ .interface = &core_comparators
+};
+
+/*
+ * Trivial/Common comparator method implementations
+ */
+
+bool sieve_comparator_octet_skip
+ (const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char **val, const char *val_end)
+{
+ if ( *val < val_end ) {
+ (*val)++;
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-comparators.h b/pigeonhole/src/lib-sieve/sieve-comparators.h
new file mode 100644
index 0000000..fdfb9ee
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-comparators.h
@@ -0,0 +1,153 @@
+#ifndef SIEVE_COMPARATORS_H
+#define SIEVE_COMPARATORS_H
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-objects.h"
+#include "sieve-code.h"
+
+/*
+ * Core comparators
+ */
+
+enum sieve_comparator_code {
+ SIEVE_COMPARATOR_I_OCTET,
+ SIEVE_COMPARATOR_I_ASCII_CASEMAP,
+ SIEVE_COMPARATOR_CUSTOM
+};
+
+extern const struct sieve_comparator_def i_octet_comparator;
+extern const struct sieve_comparator_def i_ascii_casemap_comparator;
+
+/*
+ * Comparator flags
+ */
+
+enum sieve_comparator_flags {
+ SIEVE_COMPARATOR_FLAG_ORDERING = (1 << 0),
+ SIEVE_COMPARATOR_FLAG_EQUALITY = (1 << 1),
+ SIEVE_COMPARATOR_FLAG_PREFIX_MATCH = (1 << 2),
+ SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH = (1 << 3),
+};
+
+/*
+ * Comparator definition
+ */
+
+struct sieve_comparator_def {
+ struct sieve_object_def obj_def;
+
+ unsigned int flags;
+
+ /* Equality and ordering */
+
+ int (*compare)(const struct sieve_comparator *cmp,
+ const char *val1, size_t val1_size,
+ const char *val2, size_t val2_size);
+
+ /* Prefix and substring match */
+
+ bool (*char_match)(const struct sieve_comparator *cmp,
+ const char **val, const char *val_end,
+ const char **key, const char *key_end);
+ bool (*char_skip)(const struct sieve_comparator *cmp,
+ const char **val, const char *val_end);
+};
+
+/*
+ * Comparator instance
+ */
+
+struct sieve_comparator {
+ struct sieve_object object;
+
+ const struct sieve_comparator_def *def;
+};
+
+#define SIEVE_COMPARATOR_DEFAULT(definition) \
+ { SIEVE_OBJECT_DEFAULT(definition), &(definition) }
+
+#define sieve_comparator_name(cmp) \
+ ( (cmp)->object.def->identifier )
+#define sieve_comparator_is(cmp, definition) \
+ ( (cmp)->def == &(definition) )
+
+static inline const struct sieve_comparator *sieve_comparator_copy
+(pool_t pool, const struct sieve_comparator *cmp_orig)
+{
+ struct sieve_comparator *cmp = p_new(pool, struct sieve_comparator, 1);
+
+ *cmp = *cmp_orig;
+
+ return cmp;
+}
+
+/*
+ * Comparator tagged argument
+ */
+
+extern const struct sieve_argument_def comparator_tag;
+
+static inline bool sieve_argument_is_comparator
+(struct sieve_ast_argument *arg)
+{
+ return ( arg->argument != NULL &&
+ (arg->argument->def == &comparator_tag) );
+}
+
+void sieve_comparators_link_tag
+ (struct sieve_validator *validator,
+ struct sieve_command_registration *cmd_reg, int id_code);
+bool sieve_comparator_tag_is
+ (struct sieve_ast_argument *tag, const struct sieve_comparator_def *cmp);
+const struct sieve_comparator *sieve_comparator_tag_get
+ (struct sieve_ast_argument *tag);
+
+void sieve_comparator_register
+ (struct sieve_validator *validator, const struct sieve_extension *ext,
+ const struct sieve_comparator_def *cmp);
+
+/*
+ * Comparator operand
+ */
+
+#define SIEVE_EXT_DEFINE_COMPARATOR(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_COMPARATORS(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+extern const struct sieve_operand_class sieve_comparator_operand_class;
+extern const struct sieve_operand_def comparator_operand;
+
+static inline void sieve_opr_comparator_emit
+(struct sieve_binary_block *sblock, const struct sieve_comparator *cmp)
+{
+ sieve_opr_object_emit(sblock, cmp->object.ext, cmp->object.def);
+}
+static inline bool sieve_opr_comparator_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ return sieve_opr_object_dump
+ (denv, &sieve_comparator_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_comparator_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_comparator *cmp)
+{
+ if ( !sieve_opr_object_read
+ (renv, &sieve_comparator_operand_class, address, &cmp->object) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ cmp->def = (const struct sieve_comparator_def *) cmp->object.def;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Trivial/Common comparator method implementations
+ */
+
+bool sieve_comparator_octet_skip
+ (const struct sieve_comparator *cmp ATTR_UNUSED,
+ const char **val, const char *val_end);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-config.h b/pigeonhole/src/lib-sieve/sieve-config.h
new file mode 100644
index 0000000..a0fed7c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-config.h
@@ -0,0 +1,16 @@
+#ifndef SIEVE_CONFIG_H
+#define SIEVE_CONFIG_H
+
+#include "pigeonhole-config.h"
+#include "pigeonhole-version.h"
+
+#define SIEVE_IMPLEMENTATION PIGEONHOLE_NAME " Sieve " PIGEONHOLE_VERSION_FULL
+
+#define SIEVE_SCRIPT_FILEEXT "sieve"
+#define SIEVE_BINARY_FILEEXT "svbin"
+
+#define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"
+
+#define DEFAULT_REDIRECT_DUPLICATE_PERIOD (3600 * 12)
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-dump.h b/pigeonhole/src/lib-sieve/sieve-dump.h
new file mode 100644
index 0000000..d704877
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-dump.h
@@ -0,0 +1,30 @@
+#ifndef SIEVE_DUMP_H
+#define SIEVE_DUMP_H
+
+#include "sieve-common.h"
+#include "sieve-code-dumper.h"
+#include "sieve-binary-dumper.h"
+
+/*
+ * Dumptime environment
+ */
+
+struct sieve_dumptime_env {
+ /* Dumpers */
+ struct sieve_instance *svinst;
+ struct sieve_binary_dumper *dumper;
+ struct sieve_code_dumper *cdumper;
+
+ /* Binary */
+ struct sieve_binary *sbin;
+ struct sieve_binary_block *sblock;
+
+ /* Code position */
+ const struct sieve_operation *oprtn;
+ sieve_size_t offset;
+
+ /* Output stream */
+ struct ostream *stream;
+};
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-error-private.h b/pigeonhole/src/lib-sieve/sieve-error-private.h
new file mode 100644
index 0000000..e0172b2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-error-private.h
@@ -0,0 +1,67 @@
+#ifndef SIEVE_ERROR_PRIVATE_H
+#define SIEVE_ERROR_PRIVATE_H
+
+#include "sieve-error.h"
+
+/*
+ * Initialization
+ */
+
+void sieve_errors_init(struct sieve_instance *svinst);
+void sieve_errors_deinit(struct sieve_instance *svinst);
+
+/*
+ * Error handler object
+ */
+
+struct sieve_error_handler {
+ pool_t pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+
+ unsigned int max_errors;
+
+ unsigned int errors;
+ unsigned int warnings;
+
+ void (*log)(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags,
+ const char *message);
+
+ void (*free)(struct sieve_error_handler *ehandler);
+
+ bool master_log:1; /* this logs through master log facility */
+ bool log_info:1; /* handle or discard info log */
+ bool log_debug:1; /* handle or discard debug log */
+};
+
+void sieve_error_handler_init(struct sieve_error_handler *ehandler,
+ struct sieve_instance *svinst, pool_t pool,
+ unsigned int max_errors);
+
+/*
+ * Direct handler calls
+ */
+
+void sieve_direct_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags,
+ const char *fmt, va_list args) ATTR_FORMAT(5, 0);
+
+static inline void ATTR_FORMAT(5, 6)
+sieve_direct_log(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_direct_logv(svinst, ehandler, params, flags, fmt, args);
+ va_end(args);
+}
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-error.c b/pigeonhole/src/lib-sieve/sieve-error.c
new file mode 100644
index 0000000..aae13a5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-error.c
@@ -0,0 +1,1064 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "ostream.h"
+#include "var-expand.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-error-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+
+/*
+ * Definitions
+ */
+
+#define CRITICAL_MSG \
+ "internal error occurred: refer to server log for more information."
+#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
+
+/* Logfile error handler will rotate log when it exceeds 10k bytes */
+#define LOGFILE_MAX_SIZE (10 * 1024)
+
+/*
+ * Utility
+ */
+
+const char *
+sieve_error_script_location(const struct sieve_script *script,
+ unsigned int source_line)
+{
+ const char *sname;
+
+ sname = (script == NULL ? NULL : sieve_script_name(script));
+
+ if (sname == NULL || *sname == '\0') {
+ if (source_line == 0)
+ return NULL;
+
+ return t_strdup_printf("line %d", source_line);
+ }
+
+ if (source_line == 0)
+ return sname;
+
+ return t_strdup_printf("%s: line %d", sname, source_line);
+}
+
+const char *sieve_error_from_external(const char *msg)
+{
+ char *new_msg;
+
+ if (msg == NULL || *msg == '\0')
+ return msg;
+
+ new_msg = t_strdup_noconst(msg);
+ new_msg[0] = i_tolower(new_msg[0]);
+
+ return new_msg;
+}
+
+/*
+ * Initialization
+ */
+
+void sieve_errors_init(struct sieve_instance *svinst ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+void sieve_errors_deinit(struct sieve_instance *svinst ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+/*
+ * Direct handler calls
+ */
+
+static void
+sieve_direct_master_log(struct sieve_instance *svinst,
+ const struct sieve_error_params *params,
+ const char *message)
+{
+ struct event_log_params event_params = {
+ .log_type = params->log_type,
+ .source_filename = params->csrc.filename,
+ .source_linenum = params->csrc.linenum,
+
+ .base_event = svinst->event,
+ };
+ struct event *event = (params->event != NULL ?
+ params->event : svinst->event);
+
+ if (params->location != NULL && *params->location != '\0') {
+ event_params.base_send_prefix =
+ t_strconcat(params->location, ": ", NULL);
+ }
+
+ event_log(event, &event_params, "%s", message);
+}
+
+void sieve_direct_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags,
+ const char *fmt, va_list args)
+{
+ struct event_log_params event_params = {
+ .log_type = params->log_type,
+ .source_filename = params->csrc.filename,
+ .source_linenum = params->csrc.linenum,
+ .base_event = svinst->event,
+ .base_str_out = NULL,
+ .no_send = TRUE,
+ };
+ struct event *event = (params->event != NULL ?
+ params->event : svinst->event);
+ bool event_log = FALSE, ehandler_log = FALSE;
+
+ if (ehandler != NULL) {
+ switch (params->log_type) {
+ case LOG_TYPE_ERROR:
+ ehandler_log = sieve_errors_more_allowed(ehandler);
+ break;
+ case LOG_TYPE_WARNING:
+ ehandler_log = TRUE;
+ break;
+ case LOG_TYPE_INFO:
+ ehandler_log = ehandler->log_info;
+ break;
+ case LOG_TYPE_DEBUG:
+ ehandler_log = ehandler->log_debug;
+ break;
+ case LOG_TYPE_FATAL:
+ case LOG_TYPE_PANIC:
+ case LOG_TYPE_COUNT:
+ case LOG_TYPE_OPTION:
+ i_unreached();
+ }
+ }
+
+ if (ehandler != NULL && ehandler->master_log) {
+ event_log = ehandler_log;
+ ehandler_log = FALSE;
+ }
+ if ((flags & SIEVE_ERROR_FLAG_GLOBAL) != 0) {
+ event_log = TRUE;
+ if ((flags & SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO) != 0 &&
+ params->log_type > LOG_TYPE_INFO)
+ event_params.log_type = LOG_TYPE_INFO;
+ }
+
+ if (event_log) {
+ event_params.no_send = FALSE;
+ if (params->location != NULL && *params->location != '\0') {
+ event_params.base_send_prefix =
+ t_strconcat(params->location, ": ", NULL);
+ }
+ }
+ if (ehandler_log) {
+ if (ehandler->log == NULL)
+ ehandler_log = FALSE;
+ else
+ event_params.base_str_out = t_str_new(128);
+ }
+
+ if (event_log || ehandler_log)
+ event_logv(event, &event_params, fmt, args);
+
+ if (ehandler_log) {
+ ehandler->log(ehandler, params, flags,
+ str_c(event_params.base_str_out));
+ }
+
+ if (ehandler != NULL && ehandler->pool != NULL) {
+ switch (params->log_type) {
+ case LOG_TYPE_ERROR:
+ ehandler->errors++;
+ break;
+ case LOG_TYPE_WARNING:
+ ehandler->warnings++;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * User errors
+ */
+
+void sieve_global_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args)
+{
+ sieve_direct_logv(svinst, ehandler, params,
+ SIEVE_ERROR_FLAG_GLOBAL, fmt, args);
+}
+
+void sieve_global_info_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args)
+{
+ sieve_direct_logv(svinst, ehandler, params,
+ (SIEVE_ERROR_FLAG_GLOBAL |
+ SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO), fmt, args);
+}
+
+#undef sieve_global_error
+void sieve_global_error(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_global_logv(svinst, ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_global_warning
+void sieve_global_warning(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_global_logv(svinst, ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_global_info
+void sieve_global_info(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_INFO,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_global_logv(svinst, ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_global_info_error
+void sieve_global_info_error(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_global_info_logv(svinst, ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_global_info_warning
+void sieve_global_info_warning(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_global_info_logv(svinst, ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+/*
+ * Default (user) error functions
+ */
+
+void sieve_internal_error_params(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *user_prefix)
+{
+ char str[256];
+ const char *msg;
+ struct tm *tm;
+
+ if (ehandler == NULL || ehandler->master_log)
+ return;
+
+ tm = localtime(&ioloop_time);
+ msg = (strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
+ str : CRITICAL_MSG);
+
+ if (user_prefix == NULL || *user_prefix == '\0') {
+ sieve_direct_log(ehandler->svinst, ehandler, params, 0,
+ "%s", msg);
+ } else {
+ sieve_direct_log(ehandler->svinst, ehandler, params, 0,
+ "%s: %s", user_prefix, msg);
+ }
+}
+
+#undef sieve_internal_error
+void sieve_internal_error(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *user_prefix)
+
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+
+ sieve_internal_error_params(ehandler, &params, user_prefix);
+}
+
+void sieve_logv(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args)
+{
+ if (ehandler == NULL) return;
+
+ sieve_direct_logv(ehandler->svinst, ehandler, params, 0, fmt, args);
+}
+
+void sieve_event_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ struct event *event, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, enum sieve_error_flags flags,
+ const char *fmt, va_list args)
+{
+ struct sieve_error_params params = {
+ .log_type = log_type,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .event = event,
+ .location = location,
+ };
+
+ T_BEGIN {
+ sieve_direct_logv(svinst, ehandler, &params, flags, fmt, args);
+ } T_END;
+}
+
+
+#undef sieve_event_log
+void sieve_event_log(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ struct event *event, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, enum sieve_error_flags flags,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ sieve_event_logv(svinst, ehandler, event, log_type, csrc_filename,
+ csrc_linenum, location, flags, fmt, args);
+
+ va_end(args);
+}
+
+void sieve_criticalv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *user_prefix, const char *fmt, va_list args)
+{
+ struct sieve_error_params new_params = *params;
+
+ new_params.log_type = LOG_TYPE_ERROR;
+
+ sieve_direct_master_log(svinst, &new_params,
+ t_strdup_vprintf(fmt, args));
+ sieve_internal_error_params(ehandler, &new_params, user_prefix);
+}
+
+#undef sieve_error
+void sieve_error(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_logv(ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_warning
+void sieve_warning(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_logv(ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_info
+void sieve_info(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_INFO,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_logv(ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_debug
+void sieve_debug(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_DEBUG,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_logv(ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_critical
+void sieve_critical(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *user_prefix,
+ const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_criticalv(svinst, ehandler, &params, user_prefix,
+ fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+/*
+ * Error statistics
+ */
+
+unsigned int sieve_get_errors(struct sieve_error_handler *ehandler)
+{
+ if (ehandler == NULL || ehandler->pool == NULL)
+ return 0;
+
+ return ehandler->errors;
+}
+
+unsigned int sieve_get_warnings(struct sieve_error_handler *ehandler)
+{
+ if (ehandler == NULL || ehandler->pool == NULL)
+ return 0;
+
+ return ehandler->warnings;
+}
+
+bool sieve_errors_more_allowed(struct sieve_error_handler *ehandler)
+{
+ if (ehandler == NULL || ehandler->pool == NULL)
+ return TRUE;
+
+ return (ehandler->max_errors == 0 ||
+ ehandler->errors < ehandler->max_errors);
+}
+
+/*
+ * Error handler configuration
+ */
+
+void sieve_error_handler_accept_infolog(struct sieve_error_handler *ehandler,
+ bool enable)
+{
+ ehandler->log_info = enable;
+}
+
+void sieve_error_handler_accept_debuglog(struct sieve_error_handler *ehandler,
+ bool enable)
+{
+ ehandler->log_debug = enable;
+}
+
+/*
+ * Error handler init
+ */
+
+void sieve_error_handler_init(struct sieve_error_handler *ehandler,
+ struct sieve_instance *svinst,
+ pool_t pool, unsigned int max_errors)
+{
+ ehandler->pool = pool;
+ ehandler->svinst = svinst;
+ ehandler->refcount = 1;
+ ehandler->max_errors = max_errors;
+
+ ehandler->errors = 0;
+ ehandler->warnings = 0;
+}
+
+void sieve_error_handler_ref(struct sieve_error_handler *ehandler)
+{
+ if (ehandler == NULL || ehandler->pool == NULL)
+ return;
+
+ ehandler->refcount++;
+}
+
+void sieve_error_handler_unref(struct sieve_error_handler **ehandler)
+{
+ if (*ehandler == NULL || (*ehandler)->pool == NULL)
+ return;
+
+ i_assert((*ehandler)->refcount > 0);
+
+ if (--(*ehandler)->refcount != 0)
+ return;
+
+ if ((*ehandler)->free != NULL)
+ (*ehandler)->free(*ehandler);
+
+ pool_unref(&((*ehandler)->pool));
+ *ehandler = NULL;
+}
+
+void sieve_error_handler_reset(struct sieve_error_handler *ehandler)
+{
+ if (ehandler == NULL || ehandler->pool == NULL)
+ return;
+
+ ehandler->errors = 0;
+ ehandler->warnings = 0;
+}
+
+/*
+ * Error params utility
+ */
+
+static void
+sieve_error_params_add_prefix(struct sieve_error_handler *ehandler ATTR_UNUSED,
+ const struct sieve_error_params *params,
+ string_t *prefix)
+{
+ if (params->location != NULL && *params->location != '\0') {
+ str_append(prefix, params->location);
+ str_append(prefix, ": ");
+ }
+
+ switch (params->log_type) {
+ case LOG_TYPE_ERROR:
+ str_append(prefix, "error: ");
+ break;
+ case LOG_TYPE_WARNING:
+ str_append(prefix, "warning: ");
+ break;
+ case LOG_TYPE_INFO:
+ str_append(prefix, "info: ");
+ break;
+ case LOG_TYPE_DEBUG:
+ str_append(prefix, "debug: ");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+/*
+ * Master/System error handler
+ *
+ * - Output errors directly to Dovecot master log
+ */
+
+struct sieve_error_handler *
+sieve_master_ehandler_create(struct sieve_instance *svinst,
+ unsigned int max_errors)
+{
+ struct sieve_error_handler *ehandler;
+ pool_t pool;
+
+ pool = pool_alloconly_create("master_error_handler", 256);
+ ehandler = p_new(pool, struct sieve_error_handler, 1);
+ sieve_error_handler_init(ehandler, svinst, pool, max_errors);
+ ehandler->master_log = TRUE;
+ ehandler->log_debug = svinst->debug;
+
+ return ehandler;
+}
+
+/*
+ * STDERR error handler
+ *
+ * - Output errors directly to stderror
+ */
+
+static void
+sieve_stderr_log(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags ATTR_UNUSED,
+ const char *message)
+{
+ string_t *prefix = t_str_new(64);
+
+ sieve_error_params_add_prefix(ehandler, params, prefix);
+
+ fprintf(stderr, "%s%s.\n", str_c(prefix), message);
+}
+
+struct sieve_error_handler *
+sieve_stderr_ehandler_create(struct sieve_instance *svinst,
+ unsigned int max_errors)
+{
+ pool_t pool;
+ struct sieve_error_handler *ehandler;
+
+ /* Pool is not strictly necessary, but other handler types will need
+ * a pool, so this one will have one too.
+ */
+ pool = pool_alloconly_create("stderr_error_handler",
+ sizeof(struct sieve_error_handler));
+ ehandler = p_new(pool, struct sieve_error_handler, 1);
+ sieve_error_handler_init(ehandler, svinst, pool, max_errors);
+
+ ehandler->log = sieve_stderr_log;
+
+ return ehandler;
+}
+
+/* String buffer error handler
+ *
+ * - Output errors to a string buffer
+ */
+
+struct sieve_strbuf_ehandler {
+ struct sieve_error_handler handler;
+
+ string_t *errors;
+ bool crlf;
+};
+
+static void
+sieve_strbuf_log(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags ATTR_UNUSED, const char *message)
+{
+ struct sieve_strbuf_ehandler *handler =
+ (struct sieve_strbuf_ehandler *) ehandler;
+
+ sieve_error_params_add_prefix(ehandler, params, handler->errors);
+ str_append(handler->errors, message);
+
+ if (!handler->crlf)
+ str_append(handler->errors, ".\n");
+ else
+ str_append(handler->errors, ".\r\n");
+}
+
+struct sieve_error_handler *
+sieve_strbuf_ehandler_create(struct sieve_instance *svinst, string_t *strbuf,
+ bool crlf, unsigned int max_errors)
+{
+ pool_t pool;
+ struct sieve_strbuf_ehandler *ehandler;
+
+ pool = pool_alloconly_create("strbuf_error_handler", 256);
+ ehandler = p_new(pool, struct sieve_strbuf_ehandler, 1);
+ ehandler->errors = strbuf;
+
+ sieve_error_handler_init(&ehandler->handler, svinst, pool, max_errors);
+
+ ehandler->handler.log = sieve_strbuf_log;
+ ehandler->crlf = crlf;
+
+ return &(ehandler->handler);
+}
+
+/*
+ * Logfile error handler
+ *
+ * - Output errors to a log file
+ */
+
+struct sieve_logfile_ehandler {
+ struct sieve_error_handler handler;
+
+ const char *logfile;
+ bool started;
+ int fd;
+ struct ostream *stream;
+};
+
+static void
+sieve_logfile_write(struct sieve_logfile_ehandler *ehandler,
+ const struct sieve_error_params *params,
+ const char *message)
+{
+ string_t *outbuf;
+ ssize_t ret = 0, remain;
+ const char *data;
+
+ if (ehandler->stream == NULL)
+ return;
+
+ T_BEGIN {
+ outbuf = t_str_new(256);
+ sieve_error_params_add_prefix(&ehandler->handler,
+ params, outbuf);
+ str_append(outbuf, message);
+ str_append(outbuf, ".\n");
+
+ remain = str_len(outbuf);
+ data = (const char *) str_data(outbuf);
+
+ while (remain > 0) {
+ if ((ret = o_stream_send(ehandler->stream,
+ data, remain)) < 0)
+ break;
+
+ remain -= ret;
+ data += ret;
+ }
+ } T_END;
+
+ if (ret < 0) {
+ e_error(ehandler->handler.svinst->event,
+ "o_stream_send() failed on logfile %s: %m",
+ ehandler->logfile);
+ }
+}
+
+inline static void ATTR_FORMAT(5, 6)
+sieve_logfile_printf(struct sieve_logfile_ehandler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_INFO,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+ va_start(args, fmt);
+
+ sieve_logfile_write(ehandler, &params, t_strdup_vprintf(fmt, args));
+
+ va_end(args);
+}
+
+static void sieve_logfile_start(struct sieve_logfile_ehandler *ehandler)
+{
+ struct sieve_instance *svinst = ehandler->handler.svinst;
+ struct ostream *ostream = NULL;
+ struct stat st;
+ struct tm *tm;
+ char buf[256];
+ time_t now;
+ int fd;
+
+ /* Open the logfile */
+
+ fd = open(ehandler->logfile, O_CREAT | O_APPEND | O_WRONLY, 0600);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ e_error(svinst->event,
+ "failed to open logfile "
+ "(LOGGING TO STDERR): %s",
+ eacces_error_get_creating("open",
+ ehandler->logfile));
+ } else {
+ e_error(svinst->event, "failed to open logfile "
+ "(LOGGING TO STDERR): "
+ "open(%s) failed: %m", ehandler->logfile);
+ }
+ fd = STDERR_FILENO;
+ } else {
+ /* fd_close_on_exec(fd, TRUE); Necessary? */
+
+ /* Stat the log file to obtain size information */
+ if (fstat(fd, &st) != 0) {
+ e_error(svinst->event, "failed to stat logfile "
+ "(logging to STDERR): "
+ "fstat(fd=%s) failed: %m", ehandler->logfile);
+
+ if (close(fd) < 0) {
+ e_error(svinst->event,
+ "failed to close logfile after error: "
+ "close(fd=%s) failed: %m",
+ ehandler->logfile);
+ }
+
+ fd = STDERR_FILENO;
+ }
+
+ /* Rotate log when it has grown too large */
+ if (st.st_size >= LOGFILE_MAX_SIZE) {
+ const char *rotated;
+
+ /* Close open file */
+ if (close(fd) < 0) {
+ e_error(svinst->event,
+ "failed to close logfile: "
+ "close(fd=%s) failed: %m",
+ ehandler->logfile);
+ }
+
+ /* Rotate logfile */
+ rotated = t_strconcat(ehandler->logfile, ".0", NULL);
+ if (rename(ehandler->logfile, rotated) < 0 &&
+ errno != ENOENT) {
+ if (errno == EACCES) {
+ const char *target =
+ t_strconcat(ehandler->logfile,
+ ", ", rotated, NULL);
+ e_error(svinst->event,
+ "failed to rotate logfile: %s",
+ eacces_error_get_creating(
+ "rename", target));
+ } else {
+ e_error(svinst->event,
+ "failed to rotate logfile: "
+ "rename(%s, %s) failed: %m",
+ ehandler->logfile, rotated);
+ }
+ }
+
+ /* Open clean logfile (overwrites existing if rename() failed earlier) */
+ fd = open(ehandler->logfile,
+ O_CREAT | O_APPEND | O_WRONLY | O_TRUNC, 0600);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ e_error(svinst->event,
+ "failed to open logfile "
+ "(LOGGING TO STDERR): %s",
+ eacces_error_get_creating(
+ "open", ehandler->logfile));
+ } else {
+ e_error(svinst->event,
+ "failed to open logfile "
+ "(LOGGING TO STDERR): "
+ "open(%s) failed: %m",
+ ehandler->logfile);
+ }
+ fd = STDERR_FILENO;
+ }
+ }
+ }
+
+ ostream = o_stream_create_fd(fd, 0);
+ if (ostream == NULL) {
+ /* Can't we do anything else in this most awkward situation? */
+ e_error(svinst->event,
+ "failed to open log stream on open file: "
+ "o_stream_create_fd(fd=%s) failed "
+ "(non-critical messages are not logged!)",
+ ehandler->logfile);
+ }
+
+ ehandler->fd = fd;
+ ehandler->stream = ostream;
+ ehandler->started = TRUE;
+
+ if (ostream != NULL) {
+ now = time(NULL);
+ tm = localtime(&now);
+
+ if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z", tm) > 0) {
+ sieve_logfile_printf(ehandler, __FILE__, __LINE__,
+ "sieve", "started log at %s", buf);
+ }
+ }
+}
+
+static void
+sieve_logfile_log(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags ATTR_UNUSED,
+ const char *message)
+{
+ struct sieve_logfile_ehandler *handler =
+ (struct sieve_logfile_ehandler *) ehandler;
+
+ if (!handler->started)
+ sieve_logfile_start(handler);
+
+ sieve_logfile_write(handler, params, message);
+}
+
+static void sieve_logfile_free(struct sieve_error_handler *ehandler)
+{
+ struct sieve_logfile_ehandler *handler =
+ (struct sieve_logfile_ehandler *) ehandler;
+
+ if (handler->stream != NULL) {
+ o_stream_destroy(&(handler->stream));
+ if (handler->fd != STDERR_FILENO) {
+ if (close(handler->fd) < 0) {
+ e_error(ehandler->svinst->event,
+ "failed to close logfile: "
+ "close(fd=%s) failed: %m",
+ handler->logfile);
+ }
+ }
+ }
+}
+
+struct sieve_error_handler *
+sieve_logfile_ehandler_create(struct sieve_instance *svinst,
+ const char *logfile, unsigned int max_errors)
+{
+ pool_t pool;
+ struct sieve_logfile_ehandler *ehandler;
+
+ pool = pool_alloconly_create("logfile_error_handler", 512);
+ ehandler = p_new(pool, struct sieve_logfile_ehandler, 1);
+ sieve_error_handler_init(&ehandler->handler, svinst, pool, max_errors);
+
+ ehandler->handler.log = sieve_logfile_log;
+ ehandler->handler.free = sieve_logfile_free;
+
+ /* Don't open logfile until something is actually logged.
+ * Let's not pullute the sieve directory with useless logfiles.
+ */
+ ehandler->logfile = p_strdup(pool, logfile);
+ ehandler->started = FALSE;
+ ehandler->stream = NULL;
+ ehandler->fd = -1;
+
+ return &(ehandler->handler);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-error.h b/pigeonhole/src/lib-sieve/sieve-error.h
new file mode 100644
index 0000000..83b26b7
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-error.h
@@ -0,0 +1,235 @@
+#ifndef SIEVE_ERROR_H
+#define SIEVE_ERROR_H
+
+#include "lib.h"
+#include "compat.h"
+
+#include <stdarg.h>
+
+/*
+ * Forward declarations
+ */
+
+struct var_expand_table;
+
+struct sieve_instance;
+struct sieve_script;
+struct sieve_error_handler;
+
+/*
+ * Types
+ */
+
+enum sieve_error_flags {
+ SIEVE_ERROR_FLAG_GLOBAL = (1 << 0),
+ SIEVE_ERROR_FLAG_GLOBAL_MAX_INFO = (1 << 1),
+};
+
+struct sieve_error_params {
+ enum log_type log_type;
+ struct event *event;
+
+ /* Location log command in C source code */
+ struct {
+ const char *filename;
+ unsigned int linenum;
+ } csrc;
+
+ /* Location in Sieve source script */
+ const char *location;
+};
+
+/*
+ * Utility
+ */
+
+/* Converts external messages to a style that better matches Sieve user errors
+ */
+const char *sieve_error_from_external(const char *msg);
+
+/*
+ * Global (user+system) errors
+ */
+
+void sieve_global_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+void sieve_global_info_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args) ATTR_FORMAT(4, 0);
+
+void sieve_global_error(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(6, 7);
+#define sieve_global_error(svinst, ehandler, ...) \
+ sieve_global_error(svinst, ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_global_warning(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(6, 7);
+#define sieve_global_warning(svinst, ehandler, ...) \
+ sieve_global_warning(svinst, ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_global_info(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(6, 7);
+#define sieve_global_info(svinst, ehandler, ...) \
+ sieve_global_info(svinst, ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_global_info_error(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(6, 7);
+#define sieve_global_info_error(svinst, ehandler, ...) \
+ sieve_global_info_error(svinst, ehandler, __FILE__, __LINE__, \
+ __VA_ARGS__)
+void sieve_global_info_warning(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(6, 7);
+#define sieve_global_info_warning(svinst, ehandler, ...) \
+ sieve_global_info_warning(svinst, ehandler, __FILE__, __LINE__, \
+ __VA_ARGS__)
+
+/*
+ * Main (user) error functions
+ */
+
+/* For these functions it is the responsibility of the caller to
+ * manage the datastack.
+ */
+
+const char *
+sieve_error_script_location(const struct sieve_script *script,
+ unsigned int source_line);
+
+void sieve_logv(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+
+void sieve_event_logv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ struct event *event, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, enum sieve_error_flags flags,
+ const char *fmt, va_list args) ATTR_FORMAT(9, 0);
+void sieve_event_log(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ struct event *event, enum log_type log_type,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, enum sieve_error_flags flags,
+ const char *fmt, ...) ATTR_FORMAT(9, 10);
+#define sieve_event_log(svinst, ehandler, event, log_type, ...) \
+ sieve_event_log(svinst, ehandler, event, log_type, __FILE__, __LINE__, \
+ __VA_ARGS__)
+
+void sieve_criticalv(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *user_prefix, const char *fmt, va_list args)
+ ATTR_FORMAT(5, 0);
+
+void sieve_error(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...) ATTR_FORMAT(5, 6);
+#define sieve_error(ehandler, ...) \
+ sieve_error(ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_warning(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_warning(ehandler, ...) \
+ sieve_warning(ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_info(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...) ATTR_FORMAT(5, 6);
+#define sieve_info(ehandler, ...) \
+ sieve_info(ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_debug(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...) ATTR_FORMAT(5, 6);
+#define sieve_debug(ehandler, ...) \
+ sieve_debug(ehandler, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_critical(struct sieve_instance *svinst,
+ struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *user_prefix,
+ const char *fmt, ...) ATTR_FORMAT(7, 8);
+#define sieve_critical(svinst, ehandler, ...) \
+ sieve_critical(svinst, ehandler, __FILE__, __LINE__, __VA_ARGS__)
+
+
+void sieve_internal_error_params(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ const char *user_prefix);
+void sieve_internal_error(struct sieve_error_handler *ehandler,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *user_prefix)
+ ATTR_NULL(1, 4, 5);
+#define sieve_internal_error(ehandler, ...) \
+ sieve_internal_error(ehandler, __FILE__, __LINE__, __VA_ARGS__)
+
+/*
+ * Error handler configuration
+ */
+
+void sieve_error_handler_accept_infolog(struct sieve_error_handler *ehandler,
+ bool enable);
+void sieve_error_handler_accept_debuglog(struct sieve_error_handler *ehandler,
+ bool enable);
+
+/*
+ * Error handler statistics
+ */
+
+unsigned int sieve_get_errors(struct sieve_error_handler *ehandler);
+unsigned int sieve_get_warnings(struct sieve_error_handler *ehandler);
+
+bool sieve_errors_more_allowed(struct sieve_error_handler *ehandler);
+
+/*
+ * Error handler object
+ */
+
+void sieve_error_handler_ref(struct sieve_error_handler *ehandler);
+void sieve_error_handler_unref(struct sieve_error_handler **ehandler);
+
+void sieve_error_handler_reset(struct sieve_error_handler *ehandler);
+
+/*
+ * Error handlers
+ */
+
+/* Write errors to dovecot master log */
+struct sieve_error_handler *
+sieve_master_ehandler_create(struct sieve_instance *svinst,
+ unsigned int max_errors);
+
+/* Write errors to stderr */
+struct sieve_error_handler *
+sieve_stderr_ehandler_create(struct sieve_instance *svinst,
+ unsigned int max_errors);
+
+/* Write errors into a string buffer */
+struct sieve_error_handler *
+sieve_strbuf_ehandler_create(struct sieve_instance *svinst, string_t *strbuf,
+ bool crlf, unsigned int max_errors);
+
+/* Write errors to a logfile */
+struct sieve_error_handler *
+sieve_logfile_ehandler_create(struct sieve_instance *svinst,
+ const char *logfile, unsigned int max_errors);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-execute.c b/pigeonhole/src/lib-sieve/sieve-execute.c
new file mode 100644
index 0000000..a395cc6
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-execute.c
@@ -0,0 +1,162 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-execute.h"
+
+struct sieve_execute_state {
+ void *dup_trans;
+};
+
+struct event_category event_category_sieve_execute = {
+ .parent = &event_category_sieve,
+ .name = "sieve-execute",
+};
+
+static struct sieve_execute_state *
+sieve_execute_state_create(struct sieve_execute_env *eenv)
+{
+ return p_new(eenv->pool, struct sieve_execute_state, 1);
+}
+
+static void
+sieve_execute_state_free(struct sieve_execute_state **_estate,
+ struct sieve_execute_env *eenv)
+{
+ struct sieve_execute_state *estate = *_estate;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+
+ *_estate = NULL;
+
+ if (senv->duplicate_transaction_rollback != NULL)
+ senv->duplicate_transaction_rollback(&estate->dup_trans);
+}
+
+void sieve_execute_init(struct sieve_execute_env *eenv,
+ struct sieve_instance *svinst, pool_t pool,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ enum sieve_execute_flags flags)
+{
+ i_zero(eenv);
+ eenv->svinst = svinst;
+ eenv->pool = pool;
+ eenv->flags = flags;
+ eenv->msgdata = msgdata;
+ eenv->scriptenv = senv;
+
+ pool_ref(pool);
+ eenv->event = event_create(svinst->event);
+ event_add_category(eenv->event, &event_category_sieve_execute);
+ event_add_str(eenv->event, "message_id", msgdata->id);
+ if ((flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
+ /* Make sure important envelope fields are available */
+ event_add_str(eenv->event, "mail_from",
+ smtp_address_encode(msgdata->envelope.mail_from));
+ event_add_str(eenv->event, "rcpt_to",
+ smtp_address_encode(msgdata->envelope.rcpt_to));
+ }
+
+ eenv->state = sieve_execute_state_create(eenv);
+
+ eenv->exec_status = senv->exec_status;
+ if (eenv->exec_status == NULL)
+ eenv->exec_status = p_new(pool, struct sieve_exec_status, 1);
+ else
+ i_zero(eenv->exec_status);
+}
+
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+
+ if (status == SIEVE_EXEC_OK) {
+ if (senv->duplicate_transaction_commit != NULL) {
+ senv->duplicate_transaction_commit(
+ &eenv->state->dup_trans);
+ }
+ } else {
+ if (senv->duplicate_transaction_rollback != NULL) {
+ senv->duplicate_transaction_rollback(
+ &eenv->state->dup_trans);
+ }
+ }
+}
+
+void sieve_execute_deinit(struct sieve_execute_env *eenv)
+{
+ sieve_execute_state_free(&eenv->state, eenv);
+ event_unref(&eenv->event);
+ pool_unref(&eenv->pool);
+}
+
+/*
+ * Checking for duplicates
+ */
+
+static void *
+sieve_execute_get_dup_transaction(const struct sieve_execute_env *eenv)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+
+ if (senv->duplicate_transaction_begin == NULL)
+ return NULL;
+ if (eenv->state->dup_trans == NULL) {
+ eenv->state->dup_trans =
+ senv->duplicate_transaction_begin(senv);
+ }
+ return eenv->state->dup_trans;
+}
+
+bool sieve_execute_duplicate_check_available(
+ const struct sieve_execute_env *eenv)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+
+ return (senv->duplicate_transaction_begin != NULL);
+}
+
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+ const void *id, size_t id_size,
+ bool *duplicate_r)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+ int ret;
+
+ *duplicate_r = FALSE;
+
+ if (senv->duplicate_check == NULL)
+ return SIEVE_EXEC_OK;
+
+ e_debug(eenv->svinst->event, "Check duplicate ID");
+
+ ret = senv->duplicate_check(dup_trans, senv, id, id_size);
+ switch (ret) {
+ case SIEVE_DUPLICATE_CHECK_RESULT_EXISTS:
+ *duplicate_r = TRUE;
+ break;
+ case SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+ break;
+ case SIEVE_DUPLICATE_CHECK_RESULT_FAILURE:
+ return SIEVE_EXEC_FAILURE;
+ case SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE:
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+ const void *id, size_t id_size, time_t time)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+
+ if (senv->duplicate_mark == NULL)
+ return;
+
+ e_debug(eenv->svinst->event, "Mark ID as duplicate");
+
+ senv->duplicate_mark(dup_trans, senv, id, id_size, time);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-execute.h b/pigeonhole/src/lib-sieve/sieve-execute.h
new file mode 100644
index 0000000..8af182b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-execute.h
@@ -0,0 +1,42 @@
+#ifndef SIEVE_EXECUTE_H
+#define SIEVE_EXECUTE_H
+
+#include "sieve-common.h"
+
+struct sieve_execute_state;
+
+struct sieve_execute_env {
+ struct sieve_instance *svinst;
+ pool_t pool;
+
+ enum sieve_execute_flags flags;
+ struct event *event;
+
+ const struct sieve_message_data *msgdata;
+ const struct sieve_script_env *scriptenv;
+
+ struct sieve_execute_state *state;
+ struct sieve_exec_status *exec_status;
+};
+
+void sieve_execute_init(struct sieve_execute_env *eenv,
+ struct sieve_instance *svinst, pool_t pool,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ enum sieve_execute_flags flags);
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status);
+void sieve_execute_deinit(struct sieve_execute_env *eenv);
+
+/*
+ * Checking for duplicates
+ */
+
+bool sieve_execute_duplicate_check_available(
+ const struct sieve_execute_env *eenv);
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+ const void *id, size_t id_size,
+ bool *duplicate_r);
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+ const void *id, size_t id_size, time_t time);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-extensions.c b/pigeonhole/src/lib-sieve/sieve-extensions.c
new file mode 100644
index 0000000..a1cb810
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-extensions.c
@@ -0,0 +1,879 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+/*
+ * Forward declarations
+ */
+
+static void sieve_extension_registry_init(struct sieve_instance *svinst);
+static void sieve_extension_registry_deinit(struct sieve_instance *svinst);
+
+static void sieve_capability_registry_init(struct sieve_instance *svinst);
+static void sieve_capability_registry_deinit(struct sieve_instance *svinst);
+
+static struct sieve_extension *_sieve_extension_register
+ (struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+ bool load, bool required);
+
+/*
+ * Instance global context
+ */
+
+struct sieve_extension_registry {
+ ARRAY(struct sieve_extension *) extensions;
+ HASH_TABLE(const char *, struct sieve_extension *) extension_index;
+ HASH_TABLE(const char *, struct sieve_capability_registration *) capabilities_index;
+
+ /* Core language 'extensions' */
+ const struct sieve_extension *comparator_extension;
+ const struct sieve_extension *match_type_extension;
+ const struct sieve_extension *address_part_extension;
+
+ /* Preloaded extensions */
+ ARRAY(const struct sieve_extension *) preloaded_extensions;
+};
+
+/*
+ * Pre-loaded 'extensions'
+ */
+
+extern const struct sieve_extension_def comparator_extension;
+extern const struct sieve_extension_def match_type_extension;
+extern const struct sieve_extension_def address_part_extension;
+
+/*
+ * Dummy extensions
+ */
+
+/* FIXME: This is stupid. Define a comparator-* extension and be done with it */
+
+const struct sieve_extension_def comparator_i_octet_extension = {
+ .name = "comparator-i;octet",
+};
+
+const struct sieve_extension_def comparator_i_ascii_casemap_extension = {
+ .name = "comparator-i;ascii-casemap",
+};
+
+/*
+ * List of native extensions
+ */
+
+/* Dummy extensions */
+
+extern const struct sieve_extension_def comparator_i_octet_extension;
+extern const struct sieve_extension_def comparator_i_ascii_casemap_extension;
+
+const struct sieve_extension_def *sieve_dummy_extensions[] = {
+ &comparator_i_octet_extension, &comparator_i_ascii_casemap_extension
+};
+
+const unsigned int sieve_dummy_extensions_count =
+ N_ELEMENTS(sieve_dummy_extensions);
+
+/* Core */
+
+extern const struct sieve_extension_def fileinto_extension;
+extern const struct sieve_extension_def reject_extension;
+extern const struct sieve_extension_def envelope_extension;
+extern const struct sieve_extension_def encoded_character_extension;
+
+extern const struct sieve_extension_def vacation_extension;
+extern const struct sieve_extension_def subaddress_extension;
+extern const struct sieve_extension_def comparator_i_ascii_numeric_extension;
+extern const struct sieve_extension_def relational_extension;
+extern const struct sieve_extension_def regex_extension;
+extern const struct sieve_extension_def imap4flags_extension;
+extern const struct sieve_extension_def copy_extension;
+extern const struct sieve_extension_def include_extension;
+extern const struct sieve_extension_def body_extension;
+extern const struct sieve_extension_def variables_extension;
+extern const struct sieve_extension_def enotify_extension;
+extern const struct sieve_extension_def environment_extension;
+extern const struct sieve_extension_def mailbox_extension;
+extern const struct sieve_extension_def date_extension;
+extern const struct sieve_extension_def index_extension;
+extern const struct sieve_extension_def ihave_extension;
+extern const struct sieve_extension_def duplicate_extension;
+extern const struct sieve_extension_def mime_extension;
+extern const struct sieve_extension_def foreverypart_extension;
+extern const struct sieve_extension_def extracttext_extension;
+extern const struct sieve_extension_def mboxmetadata_extension;
+extern const struct sieve_extension_def servermetadata_extension;
+
+const struct sieve_extension_def *sieve_core_extensions[] = {
+ /* Core extensions */
+ &fileinto_extension, &reject_extension, &envelope_extension,
+ &encoded_character_extension,
+
+ /* 'Plugins' */
+ &vacation_extension, &subaddress_extension,
+ &comparator_i_ascii_numeric_extension,
+ &relational_extension, &regex_extension, &imap4flags_extension,
+ &copy_extension, &include_extension, &body_extension,
+ &variables_extension, &enotify_extension, &environment_extension,
+ &mailbox_extension, &date_extension, &index_extension, &ihave_extension,
+ &duplicate_extension, &mime_extension, &foreverypart_extension,
+ &extracttext_extension
+};
+
+const unsigned int sieve_core_extensions_count =
+ N_ELEMENTS(sieve_core_extensions);
+
+/* Extra;
+ * These are not enabled by default, e.g. because explicit configuration is
+ * necessary to make these useful.
+ */
+
+extern const struct sieve_extension_def vacation_seconds_extension;
+extern const struct sieve_extension_def spamtest_extension;
+extern const struct sieve_extension_def spamtestplus_extension;
+extern const struct sieve_extension_def virustest_extension;
+extern const struct sieve_extension_def editheader_extension;
+extern const struct sieve_extension_def special_use_extension;
+
+extern const struct sieve_extension_def vnd_debug_extension;
+extern const struct sieve_extension_def vnd_environment_extension;
+extern const struct sieve_extension_def vnd_report_extension;
+
+const struct sieve_extension_def *sieve_extra_extensions[] = {
+ &vacation_seconds_extension, &spamtest_extension, &spamtestplus_extension,
+ &virustest_extension, &editheader_extension,
+ &mboxmetadata_extension, &servermetadata_extension,
+ &special_use_extension,
+
+ /* vnd.dovecot. */
+ &vnd_debug_extension, &vnd_environment_extension, &vnd_report_extension
+};
+
+const unsigned int sieve_extra_extensions_count =
+ N_ELEMENTS(sieve_extra_extensions);
+
+/*
+ * Deprecated extensions
+ */
+
+extern const struct sieve_extension_def imapflags_extension;
+extern const struct sieve_extension_def notify_extension;
+extern const struct sieve_extension_def vnd_duplicate_extension;
+
+const struct sieve_extension_def *sieve_deprecated_extensions[] = {
+ &imapflags_extension,
+ &notify_extension,
+ &vnd_duplicate_extension
+};
+
+const unsigned int sieve_deprecated_extensions_count =
+ N_ELEMENTS(sieve_deprecated_extensions);
+
+/*
+ * Unfinished extensions
+ */
+
+#ifdef HAVE_SIEVE_UNFINISHED
+
+extern const struct sieve_extension_def ereject_extension;
+
+const struct sieve_extension_def *sieve_unfinished_extensions[] = {
+ &ereject_extension
+};
+
+const unsigned int sieve_unfinished_extensions_count =
+ N_ELEMENTS(sieve_unfinished_extensions);
+
+#endif /* HAVE_SIEVE_UNFINISHED */
+
+/*
+ * Extensions init/deinit
+ */
+
+bool sieve_extensions_init(struct sieve_instance *svinst)
+{
+ unsigned int i;
+ struct sieve_extension_registry *ext_reg =
+ p_new(svinst->pool, struct sieve_extension_registry, 1);
+ struct sieve_extension *ext;
+
+ svinst->ext_reg = ext_reg;
+
+ sieve_extension_registry_init(svinst);
+ sieve_capability_registry_init(svinst);
+
+ /* Preloaded 'extensions' */
+ ext_reg->comparator_extension =
+ sieve_extension_register(svinst, &comparator_extension, TRUE);
+ ext_reg->match_type_extension =
+ sieve_extension_register(svinst, &match_type_extension, TRUE);
+ ext_reg->address_part_extension =
+ sieve_extension_register(svinst, &address_part_extension, TRUE);
+
+ p_array_init(&ext_reg->preloaded_extensions, svinst->pool, 5);
+ array_append(&ext_reg->preloaded_extensions,
+ &ext_reg->comparator_extension, 1);
+ array_append(&ext_reg->preloaded_extensions,
+ &ext_reg->match_type_extension, 1);
+ array_append(&ext_reg->preloaded_extensions,
+ &ext_reg->address_part_extension, 1);
+
+ /* Pre-load dummy extensions */
+ for ( i = 0; i < sieve_dummy_extensions_count; i++ ) {
+ if ( (ext=_sieve_extension_register
+ (svinst, sieve_dummy_extensions[i], TRUE, FALSE)) == NULL )
+ return FALSE;
+
+ ext->dummy = TRUE;
+ }
+
+ /* Pre-load core extensions */
+ for ( i = 0; i < sieve_core_extensions_count; i++ ) {
+ if ( sieve_extension_register
+ (svinst, sieve_core_extensions[i], TRUE) == NULL )
+ return FALSE;
+ }
+
+ /* Pre-load extra extensions */
+ for ( i = 0; i < sieve_extra_extensions_count; i++ ) {
+ if ( sieve_extension_register
+ (svinst, sieve_extra_extensions[i], FALSE) == NULL )
+ return FALSE;
+ }
+
+ /* Register deprecated extensions */
+ for ( i = 0; i < sieve_deprecated_extensions_count; i++ ) {
+ if ( sieve_extension_register
+ (svinst, sieve_deprecated_extensions[i], FALSE) == NULL )
+ return FALSE;
+ }
+
+#ifdef HAVE_SIEVE_UNFINISHED
+ /* Register unfinished extensions */
+ for ( i = 0; i < sieve_unfinished_extensions_count; i++ ) {
+ if ( sieve_extension_register
+ (svinst, sieve_unfinished_extensions[i], FALSE) == NULL )
+ return FALSE;
+ }
+#endif
+
+ /* More extensions can be added through plugins */
+
+ return TRUE;
+}
+
+void sieve_extensions_configure(struct sieve_instance *svinst)
+{
+ const char *extensions;
+
+ /* Apply sieve_extensions configuration */
+
+ if ( (extensions=sieve_setting_get
+ (svinst, "sieve_extensions")) != NULL )
+ sieve_extensions_set_string(svinst, extensions, FALSE, FALSE);
+
+ /* Apply sieve_global_extensions configuration */
+
+ if ( (extensions=sieve_setting_get
+ (svinst, "sieve_global_extensions")) != NULL )
+ sieve_extensions_set_string(svinst, extensions, TRUE, FALSE);
+
+ /* Apply sieve_implicit_extensions configuration */
+
+ if ( (extensions=sieve_setting_get
+ (svinst, "sieve_implicit_extensions")) != NULL )
+ sieve_extensions_set_string(svinst, extensions, FALSE, TRUE);
+}
+
+void sieve_extensions_deinit(struct sieve_instance *svinst)
+{
+ sieve_extension_registry_deinit(svinst);
+ sieve_capability_registry_deinit(svinst);
+}
+
+/*
+ * Pre-loaded extensions
+ */
+
+const struct sieve_extension *const *sieve_extensions_get_preloaded
+(struct sieve_instance *svinst, unsigned int *count_r)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ return array_get(&ext_reg->preloaded_extensions, count_r);
+}
+
+/*
+ * Extension registry
+ */
+
+static bool _sieve_extension_load(struct sieve_extension *ext)
+{
+ /* Call load handler */
+ if ( ext->def != NULL && ext->def->load != NULL &&
+ !ext->def->load(ext, &ext->context) ) {
+ e_error(ext->svinst->event,
+ "failed to load '%s' extension support.",
+ ext->def->name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void _sieve_extension_unload(struct sieve_extension *ext)
+{
+ /* Call unload handler */
+ if ( ext->def != NULL && ext->def->unload != NULL )
+ ext->def->unload(ext);
+ ext->context = NULL;
+}
+
+static void sieve_extension_registry_init(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ p_array_init(&ext_reg->extensions, svinst->pool, 50);
+ hash_table_create
+ (&ext_reg->extension_index, default_pool, 0, str_hash, strcmp);
+}
+
+static void sieve_extension_registry_deinit(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ struct sieve_extension * const *exts;
+ unsigned int i, ext_count;
+
+ if ( !hash_table_is_created(ext_reg->extension_index) ) return;
+
+ exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+ for ( i = 0; i < ext_count; i++ ) {
+ _sieve_extension_unload(exts[i]);
+ }
+
+ hash_table_destroy(&ext_reg->extension_index);
+}
+
+bool sieve_extension_reload(const struct sieve_extension *ext)
+{
+ struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+ struct sieve_extension * const *mod_ext;
+ int ext_id = ext->id;
+
+ /* Let's not just cast the 'const' away */
+ if ( ext_id >= 0 && ext_id < (int) array_count(&ext_reg->extensions) ) {
+ mod_ext = array_idx(&ext_reg->extensions, ext_id);
+
+ return _sieve_extension_load(*mod_ext);
+ }
+
+ return FALSE;
+}
+
+static struct sieve_extension *sieve_extension_lookup
+(struct sieve_instance *svinst, const char *name)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ return hash_table_lookup(ext_reg->extension_index, name);
+}
+
+static struct sieve_extension *sieve_extension_alloc
+(struct sieve_instance *svinst,
+ const struct sieve_extension_def *extdef)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ struct sieve_extension *ext, **extr;
+ int ext_id;
+
+ ext_id = (int)array_count(&ext_reg->extensions);
+
+ /* Add extension to the registry */
+ extr = array_append_space(&ext_reg->extensions);
+ *extr = ext = p_new(svinst->pool, struct sieve_extension, 1);
+ ext->id = ext_id;
+ ext->def = extdef;
+ ext->svinst = svinst;
+ return ext;
+}
+
+static struct sieve_extension *_sieve_extension_register
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+ bool load, bool required)
+{
+ struct sieve_extension *ext;
+
+ ext = sieve_extension_lookup(svinst, extdef->name);
+
+ /* Register extension if it is not registered already */
+ if ( ext == NULL ) {
+ ext = sieve_extension_alloc(svinst, extdef);
+ hash_table_insert
+ (svinst->ext_reg->extension_index, extdef->name, ext);
+
+ } else if ( ext->overridden ) {
+ /* Create a dummy */
+ ext = sieve_extension_alloc(svinst, extdef);
+
+ } else {
+ /* Re-register it if it were previously unregistered
+ * (not going to happen)
+ */
+ i_assert( ext->def == NULL || ext->def == extdef );
+ ext->def = extdef;
+ }
+
+ /* Enable extension */
+ if ( load || required ) {
+ ext->enabled = ( ext->enabled || load );
+
+ /* Call load handler if extension was not loaded already */
+ if ( !ext->loaded ) {
+ if ( !_sieve_extension_load(ext) )
+ return NULL;
+ }
+
+ ext->loaded = TRUE;
+ }
+
+ ext->required = ( ext->required || required );
+
+ return ext;
+}
+
+const struct sieve_extension *sieve_extension_register
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+ bool load)
+{
+ return _sieve_extension_register(svinst, extdef, load, FALSE);
+}
+
+void sieve_extension_unregister(const struct sieve_extension *ext)
+{
+ struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+ struct sieve_extension * const *mod_ext;
+ int ext_id = ext->id;
+
+ if ( ext_id >= 0 && ext_id < (int) array_count(&ext_reg->extensions) ) {
+ mod_ext = array_idx(&ext_reg->extensions, ext_id);
+
+ sieve_extension_capabilities_unregister(*mod_ext);
+ _sieve_extension_unload(*mod_ext);
+ (*mod_ext)->loaded = FALSE;
+ (*mod_ext)->enabled = FALSE;
+ (*mod_ext)->def = NULL;
+ }
+}
+
+const struct sieve_extension *sieve_extension_replace
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+ bool load)
+{
+ struct sieve_extension *ext;
+
+ ext = sieve_extension_lookup(svinst, extdef->name);
+ if (ext != NULL)
+ sieve_extension_unregister(ext);
+ return sieve_extension_register(svinst, extdef, load);
+}
+
+const struct sieve_extension *sieve_extension_require
+(struct sieve_instance *svinst, const struct sieve_extension_def *extdef,
+ bool load)
+{
+ return _sieve_extension_register(svinst, extdef, load, TRUE);
+}
+
+void sieve_extension_override
+(struct sieve_instance *svinst, const char *name,
+ const struct sieve_extension *ext)
+{
+ struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+ struct sieve_extension * const *mod_ext;
+ struct sieve_extension *old_ext;
+
+ old_ext = sieve_extension_lookup(svinst, name);
+ if (old_ext == ext)
+ return;
+ i_assert( old_ext == NULL || !old_ext->overridden );
+
+ i_assert( ext->id >= 0 &&
+ ext->id < (int) array_count(&ext_reg->extensions) );
+ mod_ext = array_idx(&ext_reg->extensions, ext->id);
+
+ hash_table_update
+ (ext_reg->extension_index, name, *mod_ext);
+ if ( old_ext != NULL )
+ old_ext->overridden = TRUE;
+}
+
+unsigned int sieve_extensions_get_count(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ return array_count(&ext_reg->extensions);
+}
+
+const struct sieve_extension *const *
+sieve_extensions_get_all(struct sieve_instance *svinst,
+ unsigned int *count_r)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ return (const struct sieve_extension *const *)
+ array_get(&ext_reg->extensions, count_r);
+}
+
+const struct sieve_extension *sieve_extension_get_by_id
+(struct sieve_instance *svinst, unsigned int ext_id)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ struct sieve_extension * const *ext;
+
+ if ( ext_id < array_count(&ext_reg->extensions) ) {
+ ext = array_idx(&ext_reg->extensions, ext_id);
+
+ if ( (*ext)->def != NULL && ((*ext)->enabled || (*ext)->required) )
+ return *ext;
+ }
+
+ return NULL;
+}
+
+const struct sieve_extension *sieve_extension_get_by_name
+(struct sieve_instance *svinst, const char *name)
+{
+ const struct sieve_extension *ext;
+
+ if ( *name == '@' )
+ return NULL;
+
+ if ( strlen(name) > 128 )
+ return NULL;
+
+ ext = sieve_extension_lookup(svinst, name);
+ if ( ext == NULL || ext->def == NULL || (!ext->enabled && !ext->required))
+ return NULL;
+
+ return ext;
+}
+
+static inline bool _sieve_extension_listable(const struct sieve_extension *ext)
+{
+ return ( ext->enabled && ext->def != NULL && *(ext->def->name) != '@'
+ && !ext->dummy && !ext->global && !ext->overridden);
+}
+
+const char *sieve_extensions_get_string(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ string_t *extstr = t_str_new(256);
+ struct sieve_extension * const *exts;
+ unsigned int i, ext_count;
+
+ exts = array_get(&ext_reg->extensions, &ext_count);
+
+ if ( ext_count > 0 ) {
+ i = 0;
+
+ /* Find first listable extension */
+ while ( i < ext_count && !_sieve_extension_listable(exts[i]) )
+ i++;
+
+ if ( i < ext_count ) {
+ /* Add first to string */
+ str_append(extstr, exts[i]->def->name);
+ i++;
+
+ /* Add others */
+ for ( ; i < ext_count; i++ ) {
+ if ( _sieve_extension_listable(exts[i]) ) {
+ str_append_c(extstr, ' ');
+ str_append(extstr, exts[i]->def->name);
+ }
+ }
+ }
+ }
+
+ return str_c(extstr);
+}
+
+static void sieve_extension_set_enabled
+(struct sieve_extension *ext, bool enabled)
+{
+ if ( enabled ) {
+ ext->enabled = TRUE;
+
+ if ( !ext->loaded ) {
+ (void)_sieve_extension_load(ext);
+ }
+
+ ext->loaded = TRUE;
+ } else {
+ ext->enabled = FALSE;
+ }
+}
+
+static void sieve_extension_set_global
+(struct sieve_extension *ext, bool enabled)
+{
+ if ( enabled ) {
+ sieve_extension_set_enabled(ext, TRUE);
+ ext->global = TRUE;
+ } else {
+ ext->global = FALSE;
+ }
+}
+
+static void sieve_extension_set_implicit
+(struct sieve_extension *ext, bool enabled)
+{
+ if ( enabled ) {
+ sieve_extension_set_enabled(ext, TRUE);
+ ext->implicit = TRUE;
+ } else {
+ ext->implicit = FALSE;
+ }
+}
+
+void sieve_extensions_set_string
+(struct sieve_instance *svinst, const char *ext_string,
+ bool global, bool implicit)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ ARRAY(const struct sieve_extension *) enabled_extensions;
+ ARRAY(const struct sieve_extension *) disabled_extensions;
+ const struct sieve_extension *const *ext_enabled;
+ const struct sieve_extension *const *ext_disabled;
+ struct sieve_extension **exts;
+ const char **ext_names;
+ unsigned int i, ext_count, ena_count, dis_count;
+ bool relative = FALSE;
+
+ if ( ext_string == NULL ) {
+ if ( global || implicit ) return;
+
+ /* Enable all */
+ exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+
+ for ( i = 0; i < ext_count; i++ )
+ sieve_extension_set_enabled(exts[i], TRUE);
+
+ return;
+ }
+
+ T_BEGIN {
+ t_array_init(&enabled_extensions, array_count(&ext_reg->extensions));
+ t_array_init(&disabled_extensions, array_count(&ext_reg->extensions));
+
+ ext_names = t_strsplit_spaces(ext_string, " \t");
+
+ while ( *ext_names != NULL ) {
+ const char *name = *ext_names;
+
+ ext_names++;
+
+ if ( *name != '\0' ) {
+ const struct sieve_extension *ext;
+ char op = '\0'; /* No add/remove operation */
+
+ if ( *name == '+' /* Add to existing config */
+ || *name == '-' ) { /* Remove from existing config */
+ op = *name++;
+ relative = TRUE;
+ }
+
+ if ( *name == '@' )
+ ext = NULL;
+ else
+ ext = hash_table_lookup(ext_reg->extension_index, name);
+
+ if ( ext == NULL || ext->def == NULL ) {
+ e_warning(svinst->event,
+ "ignored unknown extension '%s' while configuring "
+ "available extensions", name);
+ continue;
+ }
+
+ if ( op == '-' )
+ array_append(&disabled_extensions, &ext, 1);
+ else
+ array_append(&enabled_extensions, &ext, 1);
+ }
+ }
+
+ exts = array_get_modifiable(&ext_reg->extensions, &ext_count);
+ ext_enabled = array_get(&enabled_extensions, &ena_count);
+ ext_disabled = array_get(&disabled_extensions, &dis_count);
+
+ /* Set new extension status */
+
+ for ( i = 0; i < ext_count; i++ ) {
+ unsigned int j;
+ bool enabled = FALSE;
+
+ if ( exts[i]->id < 0 || exts[i]->def == NULL ||
+ *(exts[i]->def->name) == '@' ) {
+ continue;
+ }
+
+ /* If extensions are specified relative to the default set,
+ * we first need to check which ones are disabled
+ */
+
+ if ( relative ) {
+ if ( global )
+ enabled = exts[i]->global;
+ else if ( implicit )
+ enabled = exts[i]->implicit;
+ else
+ enabled = exts[i]->enabled;
+
+ if ( enabled ) {
+ /* Disable if explicitly disabled */
+ for ( j = 0; j < dis_count; j++ ) {
+ if ( ext_disabled[j]->def == exts[i]->def ) {
+ enabled = FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Enable if listed with '+' or no prefix */
+
+ for ( j = 0; j < ena_count; j++ ) {
+ if ( ext_enabled[j]->def == exts[i]->def ) {
+ enabled = TRUE;
+ break;
+ }
+ }
+
+ /* Perform actual activation/deactivation */
+ if ( global ) {
+ sieve_extension_set_global(exts[i], enabled);
+ } else if ( implicit ) {
+ sieve_extension_set_implicit(exts[i], enabled);
+ } else {
+ sieve_extension_set_enabled(exts[i], enabled);
+ }
+ }
+ } T_END;
+}
+
+const struct sieve_extension *sieve_get_match_type_extension
+ (struct sieve_instance *svinst)
+{
+ return svinst->ext_reg->match_type_extension;
+}
+
+const struct sieve_extension *sieve_get_comparator_extension
+ (struct sieve_instance *svinst)
+{
+ return svinst->ext_reg->comparator_extension;
+}
+
+const struct sieve_extension *sieve_get_address_part_extension
+ (struct sieve_instance *svinst)
+{
+ return svinst->ext_reg->address_part_extension;
+}
+
+void sieve_enable_debug_extension(struct sieve_instance *svinst)
+{
+ (void) sieve_extension_register(svinst, &vnd_debug_extension, TRUE);
+}
+
+/*
+ * Extension capabilities
+ */
+
+struct sieve_capability_registration {
+ const struct sieve_extension *ext;
+ const struct sieve_extension_capabilities *capabilities;
+};
+
+void sieve_capability_registry_init(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ hash_table_create
+ (&ext_reg->capabilities_index, default_pool, 0, str_hash, strcmp);
+}
+
+void sieve_capability_registry_deinit(struct sieve_instance *svinst)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+
+ if ( !hash_table_is_created(ext_reg->capabilities_index) ) return;
+
+ hash_table_destroy(&svinst->ext_reg->capabilities_index);
+}
+
+void sieve_extension_capabilities_register
+(const struct sieve_extension *ext,
+ const struct sieve_extension_capabilities *cap)
+{
+ struct sieve_instance *svinst = ext->svinst;
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ struct sieve_capability_registration *reg;
+
+ reg = hash_table_lookup(ext_reg->capabilities_index, cap->name);
+ if (reg != NULL) {
+ /* Already registered */
+ return;
+ }
+
+ reg = p_new(svinst->pool, struct sieve_capability_registration, 1);
+ reg->ext = ext;
+ reg->capabilities = cap;
+
+ hash_table_insert(ext_reg->capabilities_index, cap->name, reg);
+}
+
+void sieve_extension_capabilities_unregister
+(const struct sieve_extension *ext)
+{
+ struct sieve_extension_registry *ext_reg = ext->svinst->ext_reg;
+ struct hash_iterate_context *hictx;
+ const char *name;
+ struct sieve_capability_registration *reg;
+
+ hictx = hash_table_iterate_init(ext_reg->capabilities_index);
+ while ( hash_table_iterate(hictx, ext_reg->capabilities_index, &name, &reg) ) {
+ if ( reg->ext == ext )
+ hash_table_remove(ext_reg->capabilities_index, name);
+ }
+ hash_table_iterate_deinit(&hictx);
+}
+
+const char *sieve_extension_capabilities_get_string
+(struct sieve_instance *svinst, const char *cap_name)
+{
+ struct sieve_extension_registry *ext_reg = svinst->ext_reg;
+ const struct sieve_capability_registration *cap_reg =
+ hash_table_lookup(ext_reg->capabilities_index, cap_name);
+ const struct sieve_extension_capabilities *cap;
+
+ if ( cap_reg == NULL || cap_reg->capabilities == NULL )
+ return NULL;
+
+ cap = cap_reg->capabilities;
+
+ if ( cap->get_string == NULL || !cap_reg->ext->enabled )
+ return NULL;
+
+ return cap->get_string(cap_reg->ext);
+}
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/sieve-extensions.h b/pigeonhole/src/lib-sieve/sieve-extensions.h
new file mode 100644
index 0000000..961cf41
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-extensions.h
@@ -0,0 +1,191 @@
+#ifndef SIEVE_EXTENSIONS_H
+#define SIEVE_EXTENSIONS_H
+
+#include "lib.h"
+#include "sieve-common.h"
+
+/*
+ * Per-extension object registry
+ */
+
+struct sieve_extension_objects {
+ const void *objects;
+ unsigned int count;
+};
+
+/*
+ * Extension definition
+ */
+
+struct sieve_extension_def {
+ const char *name;
+
+ /* Version */
+ unsigned int version;
+
+ /* Registration */
+ bool (*load)(const struct sieve_extension *ext, void **context);
+ void (*unload)(const struct sieve_extension *ext);
+
+ /* Compilation */
+ bool (*validator_load)
+ (const struct sieve_extension *ext, struct sieve_validator *validator);
+ bool (*generator_load)
+ (const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+ bool (*interpreter_load)
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+ bool (*binary_load)
+ (const struct sieve_extension *ext, struct sieve_binary *binary);
+
+ /* Code dump */
+ bool (*binary_dump)
+ (const struct sieve_extension *ext, struct sieve_dumptime_env *denv);
+ bool (*code_dump)
+ (const struct sieve_extension *ext, const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+
+ /* Objects */
+ struct sieve_extension_objects operations;
+ struct sieve_extension_objects operands;
+};
+
+/* Defining opcodes and operands */
+
+#define SIEVE_EXT_DEFINE_NO_OBJECTS \
+ { NULL, 0 }
+#define SIEVE_EXT_DEFINE_OBJECT(OBJ) \
+ { &OBJ, 1 }
+#define SIEVE_EXT_DEFINE_OBJECTS(OBJS) \
+ { OBJS, N_ELEMENTS(OBJS) }
+
+#define SIEVE_EXT_GET_OBJECTS_COUNT(ext, field) \
+ ext->field->count;
+
+#define SIEVE_EXT_DEFINE_NO_OPERATIONS \
+ .operations = SIEVE_EXT_DEFINE_NO_OBJECTS
+#define SIEVE_EXT_DEFINE_OPERATION(OP) \
+ .operations = SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_OPERATIONS(OPS) \
+ .operations = SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+#define SIEVE_EXT_DEFINE_NO_OPERANDS \
+ .operands = SIEVE_EXT_DEFINE_NO_OBJECTS
+#define SIEVE_EXT_DEFINE_OPERAND(OP) \
+ .operands = SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_OPERANDS(OPS) \
+ .operands = SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+/*
+ * Extension instance
+ */
+
+struct sieve_extension {
+ const struct sieve_extension_def *def;
+ int id;
+
+ struct sieve_instance *svinst;
+ void *context;
+
+ bool required:1;
+ bool loaded:1;
+ bool enabled:1;
+ bool dummy:1;
+ bool global:1;
+ bool implicit:1;
+ bool overridden:1;
+};
+
+#define sieve_extension_is(ext, definition) \
+ ( (ext)->def == &(definition) )
+#define sieve_extension_name(ext) \
+ ((ext)->def->name)
+#define sieve_extension_name_is(ext, _name) \
+ ( strcmp((ext)->def->name, (_name)) == 0 )
+#define sieve_extension_version(ext) \
+ ((ext)->def->version)
+#define sieve_extension_version_is(ext, _version) \
+ ((ext)->def->version == (_version))
+
+/*
+ * Extensions init/deinit
+ */
+
+bool sieve_extensions_init(struct sieve_instance *svinst);
+void sieve_extensions_configure(struct sieve_instance *svinst);
+void sieve_extensions_deinit(struct sieve_instance *svinst);
+
+/*
+ * Pre-loaded extensions
+ */
+
+const struct sieve_extension *const *sieve_extensions_get_preloaded
+ (struct sieve_instance *svinst, unsigned int *count_r);
+
+/*
+ * Extension registry
+ */
+
+const struct sieve_extension *sieve_extension_register
+ (struct sieve_instance *svinst, const struct sieve_extension_def *extension,
+ bool load);
+const struct sieve_extension *sieve_extension_require
+ (struct sieve_instance *svinst, const struct sieve_extension_def *extension,
+ bool load);
+bool sieve_extension_reload(const struct sieve_extension *ext);
+
+void sieve_extension_unregister(const struct sieve_extension *ext);
+
+const struct sieve_extension *sieve_extension_replace
+ (struct sieve_instance *svinst,
+ const struct sieve_extension_def *extdef,
+ bool load);
+void sieve_extension_override
+ (struct sieve_instance *svinst, const char *name,
+ const struct sieve_extension *ext);
+
+unsigned int sieve_extensions_get_count(struct sieve_instance *svinst);
+const struct sieve_extension *const *
+sieve_extensions_get_all(struct sieve_instance *svinst,
+ unsigned int *count_r);
+
+const struct sieve_extension *sieve_extension_get_by_id
+ (struct sieve_instance *svinst, unsigned int ext_id);
+const struct sieve_extension *sieve_extension_get_by_name
+ (struct sieve_instance *svinst, const char *name);
+
+const char *sieve_extensions_get_string
+ (struct sieve_instance *svinst);
+void sieve_extensions_set_string
+ (struct sieve_instance *svinst, const char *ext_string,
+ bool global, bool implicit);
+
+const struct sieve_extension *sieve_get_match_type_extension
+ (struct sieve_instance *svinst);
+const struct sieve_extension *sieve_get_comparator_extension
+ (struct sieve_instance *svinst);
+const struct sieve_extension *sieve_get_address_part_extension
+ (struct sieve_instance *svinst);
+
+void sieve_enable_debug_extension(struct sieve_instance *svinst);
+
+/*
+ * Capability registries
+ */
+
+struct sieve_extension_capabilities {
+ const char *name;
+
+ const char *(*get_string)(const struct sieve_extension *ext);
+};
+
+void sieve_extension_capabilities_register
+ (const struct sieve_extension *ext,
+ const struct sieve_extension_capabilities *cap);
+void sieve_extension_capabilities_unregister
+ (const struct sieve_extension *ext);
+
+const char *sieve_extension_capabilities_get_string
+ (struct sieve_instance *svinst, const char *cap_name);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-generator.c b/pigeonhole/src/lib-sieve/sieve-generator.c
new file mode 100644
index 0000000..ebc06ed
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-generator.c
@@ -0,0 +1,578 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+#include "sieve-generator.h"
+
+/*
+ * Jump list
+ */
+
+struct sieve_jumplist *
+sieve_jumplist_create(pool_t pool, struct sieve_binary_block *sblock)
+{
+ struct sieve_jumplist *jlist;
+
+ jlist = p_new(pool, struct sieve_jumplist, 1);
+ jlist->block = sblock;
+ p_array_init(&jlist->jumps, pool, 4);
+
+ return jlist;
+}
+
+void sieve_jumplist_init_temp(struct sieve_jumplist *jlist,
+ struct sieve_binary_block *sblock)
+{
+ jlist->block = sblock;
+ t_array_init(&jlist->jumps, 4);
+}
+
+void sieve_jumplist_reset(struct sieve_jumplist *jlist)
+{
+ array_clear(&jlist->jumps);
+}
+
+void sieve_jumplist_add(struct sieve_jumplist *jlist, sieve_size_t jump)
+{
+ array_append(&jlist->jumps, &jump, 1);
+}
+
+void sieve_jumplist_resolve(struct sieve_jumplist *jlist)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_count(&jlist->jumps); i++) {
+ const sieve_size_t *jump = array_idx(&jlist->jumps, i);
+
+ sieve_binary_resolve_offset(jlist->block, *jump);
+ }
+}
+
+/*
+ * Code Generator
+ */
+
+struct sieve_generator {
+ pool_t pool;
+
+ struct sieve_instance *instance;
+
+ struct sieve_error_handler *ehandler;
+
+ struct sieve_codegen_env genenv;
+ struct sieve_binary_debug_writer *dwriter;
+
+ ARRAY(void *) ext_contexts;
+};
+
+struct sieve_generator *
+sieve_generator_create(struct sieve_ast *ast,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags)
+{
+ pool_t pool;
+ struct sieve_generator *gentr;
+ struct sieve_script *script;
+ struct sieve_instance *svinst;
+
+ pool = pool_alloconly_create("sieve_generator", 4096);
+ gentr = p_new(pool, struct sieve_generator, 1);
+ gentr->pool = pool;
+
+ gentr->ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ gentr->genenv.gentr = gentr;
+ gentr->genenv.flags = flags;
+ gentr->genenv.ast = ast;
+ sieve_ast_ref(ast);
+
+ script = sieve_ast_script(ast);
+ svinst = sieve_script_svinst(script);
+
+ gentr->genenv.script = script;
+ gentr->genenv.svinst = svinst;
+
+ /* Setup storage for extension contexts */
+ p_array_init(&gentr->ext_contexts, pool,
+ sieve_extensions_get_count(svinst));
+
+ return gentr;
+}
+
+void sieve_generator_free(struct sieve_generator **gentr)
+{
+ sieve_ast_unref(&(*gentr)->genenv.ast);
+
+ sieve_error_handler_unref(&(*gentr)->ehandler);
+ sieve_binary_debug_writer_deinit(&(*gentr)->dwriter);
+
+ sieve_binary_unref(&(*gentr)->genenv.sbin);
+
+ pool_unref(&((*gentr)->pool));
+
+ *gentr = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *
+sieve_generator_error_handler(struct sieve_generator *gentr)
+{
+ return gentr->ehandler;
+}
+
+pool_t sieve_generator_pool(struct sieve_generator *gentr)
+{
+ return gentr->pool;
+}
+
+struct sieve_script *sieve_generator_script(struct sieve_generator *gentr)
+{
+ return gentr->genenv.script;
+}
+
+struct sieve_binary *sieve_generator_get_binary(struct sieve_generator *gentr)
+{
+ return gentr->genenv.sbin;
+}
+
+struct sieve_binary_block *
+sieve_generator_get_block(struct sieve_generator *gentr)
+{
+ return gentr->genenv.sblock;
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_generator_extension_set_context(struct sieve_generator *gentr,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ if (ext->id < 0)
+ return;
+
+ array_idx_set(&gentr->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *
+sieve_generator_extension_get_context(struct sieve_generator *gentr,
+ const struct sieve_extension *ext)
+{
+ void * const *ctx;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&gentr->ext_contexts))
+ return NULL;
+
+ ctx = array_idx(&gentr->ext_contexts, (unsigned int) ext->id);
+
+ return *ctx;
+}
+
+/*
+ * Code generation API
+ */
+
+static void
+sieve_generate_debug_from_ast_node(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *ast_node)
+{
+ sieve_size_t address = sieve_binary_block_get_size(cgenv->sblock);
+ unsigned int line = sieve_ast_node_line(ast_node);
+
+ sieve_binary_debug_emit(cgenv->gentr->dwriter, address, line, 0);
+}
+
+static void
+sieve_generate_debug_from_ast_argument(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *ast_arg)
+{
+ sieve_size_t address = sieve_binary_block_get_size(cgenv->sblock);
+ unsigned int line = sieve_ast_argument_line(ast_arg);
+
+ sieve_binary_debug_emit(cgenv->gentr->dwriter, address, line, 0);
+}
+
+bool sieve_generate_argument(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ const struct sieve_argument_def *arg_def;
+
+ if (arg->argument == NULL || arg->argument->def == NULL)
+ return FALSE;
+
+ arg_def = arg->argument->def;
+
+ if (arg_def->generate == NULL)
+ return TRUE;
+
+ sieve_generate_debug_from_ast_argument(cgenv, arg);
+
+ return arg_def->generate(cgenv, arg, cmd);
+}
+
+bool sieve_generate_arguments(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument **last_arg_r)
+{
+ enum { ARG_START, ARG_OPTIONAL, ARG_POSITIONAL } state = ARG_START;
+ struct sieve_ast_argument *arg =
+ sieve_ast_argument_first(cmd->ast_node);
+
+ /* Generate all arguments with assigned generator function */
+
+ while (arg != NULL) {
+ const struct sieve_argument *argument;
+ const struct sieve_argument_def *arg_def;
+
+ if (arg->argument == NULL || arg->argument->def == NULL)
+ return FALSE;
+
+ argument = arg->argument;
+ arg_def = argument->def;
+
+ switch (state) {
+ case ARG_START:
+ if (argument->id_code == 0)
+ state = ARG_POSITIONAL;
+ else {
+ /* Mark start of optional operands with 0
+ operand identifier */
+ sieve_binary_emit_byte(cgenv->sblock,
+ SIEVE_OPERAND_OPTIONAL);
+
+ /* Emit argument id for optional operand */
+ sieve_binary_emit_byte(
+ cgenv->sblock,
+ (unsigned char)argument->id_code);
+
+ state = ARG_OPTIONAL;
+ }
+ break;
+ case ARG_OPTIONAL:
+ if (argument->id_code == 0)
+ state = ARG_POSITIONAL;
+
+ /* Emit argument id for optional operand (0 marks the
+ end of the optionals) */
+ sieve_binary_emit_byte(
+ cgenv->sblock,
+ (unsigned char)argument->id_code);
+ break;
+ case ARG_POSITIONAL:
+ if (argument->id_code != 0)
+ return FALSE;
+ break;
+ }
+
+ /* Call the generation function for the argument */
+ if (arg_def->generate != NULL) {
+ sieve_generate_debug_from_ast_argument(cgenv, arg);
+
+ if (!arg_def->generate(cgenv, arg, cmd))
+ return FALSE;
+ } else if (state == ARG_POSITIONAL) {
+ break;
+ }
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Mark end of optional list if it is still open */
+ if (state == ARG_OPTIONAL)
+ sieve_binary_emit_byte(cgenv->sblock, 0);
+
+ if (last_arg_r != NULL)
+ *last_arg_r = arg;
+
+ return TRUE;
+}
+
+bool sieve_generate_argument_parameters(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg)
+{
+ struct sieve_ast_argument *param = arg->parameters;
+
+ /* Generate all parameters with assigned generator function */
+
+ while (param != NULL) {
+ if (param->argument != NULL && param->argument->def != NULL) {
+ const struct sieve_argument_def *parameter =
+ param->argument->def;
+
+ /* Call the generation function for the parameter */
+ if (parameter->generate != NULL) {
+ sieve_generate_debug_from_ast_argument(
+ cgenv, param);
+
+ if (!parameter->generate(cgenv, param, cmd))
+ return FALSE;
+ }
+ }
+
+ param = sieve_ast_argument_next(param);
+ }
+
+ return TRUE;
+}
+
+bool sieve_generate_test(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *tst_node,
+ struct sieve_jumplist *jlist, bool jump_true)
+{
+ struct sieve_command *test;
+ const struct sieve_command_def *tst_def;
+
+ i_assert(tst_node->command != NULL && tst_node->command->def != NULL);
+
+ test = tst_node->command;
+ tst_def = test->def;
+
+ if (tst_def->control_generate != NULL) {
+ sieve_generate_debug_from_ast_node(cgenv, tst_node);
+
+ if (tst_def->control_generate(cgenv, test, jlist, jump_true))
+ return TRUE;
+
+ return FALSE;
+ }
+
+ if (tst_def->generate != NULL) {
+ sieve_generate_debug_from_ast_node(cgenv, tst_node);
+
+ if (tst_def->generate(cgenv, test)) {
+
+ if (jump_true) {
+ sieve_operation_emit(cgenv->sblock, NULL,
+ &sieve_jmptrue_operation);
+ } else {
+ sieve_operation_emit(cgenv->sblock, NULL,
+ &sieve_jmpfalse_operation);
+ }
+ sieve_jumplist_add(
+ jlist,
+ sieve_binary_emit_offset(cgenv->sblock, 0));
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_generate_command(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *cmd_node)
+{
+ struct sieve_command *command;
+ const struct sieve_command_def *cmd_def;
+
+ i_assert(cmd_node->command != NULL && cmd_node->command->def != NULL);
+
+ command = cmd_node->command;
+ cmd_def = command->def;
+
+ if (cmd_def->generate != NULL) {
+ sieve_generate_debug_from_ast_node(cgenv, cmd_node);
+
+ return cmd_def->generate(cgenv, command);
+ }
+
+ return TRUE;
+}
+
+bool sieve_generate_block(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *block)
+{
+ bool result = TRUE;
+ struct sieve_ast_node *cmd_node;
+
+ T_BEGIN {
+ cmd_node = sieve_ast_command_first(block);
+ while (result && cmd_node != NULL) {
+ result = sieve_generate_command(cgenv, cmd_node);
+ cmd_node = sieve_ast_command_next(cmd_node);
+ }
+ } T_END;
+
+ return result;
+}
+
+struct sieve_binary *
+sieve_generator_run(struct sieve_generator *gentr,
+ struct sieve_binary_block **sblock_r)
+{
+ bool topmost = (sblock_r == NULL || *sblock_r == NULL);
+ struct sieve_binary *sbin;
+ struct sieve_binary_block *sblock, *debug_block;
+ const struct sieve_extension *const *extensions;
+ unsigned int i, ext_count;
+ bool result = TRUE;
+
+ /* Initialize */
+
+ if (topmost) {
+ sbin = sieve_binary_create_new(
+ sieve_ast_script(gentr->genenv.ast));
+ sblock = sieve_binary_block_get(
+ sbin, SBIN_SYSBLOCK_MAIN_PROGRAM);
+ } else {
+ sblock = *sblock_r;
+ sbin = sieve_binary_block_get_binary(sblock);
+ }
+
+ i_assert(sbin != NULL);
+
+ gentr->genenv.sbin = sbin;
+ gentr->genenv.sblock = sblock;
+ sieve_binary_ref(gentr->genenv.sbin);
+
+ /* Create debug block */
+ debug_block = sieve_binary_block_create(sbin);
+ gentr->dwriter = sieve_binary_debug_writer_init(debug_block);
+ (void)sieve_binary_emit_unsigned(
+ sblock, sieve_binary_block_get_id(debug_block));
+
+ /* Load extensions linked to the AST and emit a list in code */
+ extensions = sieve_ast_extensions_get(gentr->genenv.ast, &ext_count);
+ (void) sieve_binary_emit_unsigned(sblock, ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension *ext = extensions[i];
+ bool deferred;
+
+ /* Link to binary */
+ (void)sieve_binary_extension_link(sbin, ext);
+
+ /* Emit */
+ sieve_binary_emit_extension(sblock, ext, 0);
+
+ /* Emit deferred flag */
+ deferred = !sieve_ast_extension_is_required(
+ gentr->genenv.ast, ext);
+ sieve_binary_emit_byte(sblock, (deferred ? 1 : 0));
+
+ /* Load */
+ if (ext->def != NULL && ext->def->generator_load != NULL &&
+ !ext->def->generator_load(ext, &gentr->genenv))
+ result = FALSE;
+ }
+
+ /* Generate code */
+
+ if (result) {
+ if (!sieve_generate_block(&gentr->genenv,
+ sieve_ast_root(gentr->genenv.ast))) {
+ result = FALSE;
+ } else if (topmost) {
+ sieve_binary_activate(sbin);
+ }
+ }
+
+ /* Cleanup */
+
+ sieve_binary_unref(&gentr->genenv.sbin);
+ gentr->genenv.sblock = NULL;
+
+ if (!result) {
+ if (topmost) {
+ sieve_binary_unref(&sbin);
+ if (sblock_r != NULL)
+ *sblock_r = NULL;
+ }
+ sbin = NULL;
+ } else {
+ if (sblock_r != NULL)
+ *sblock_r = sblock;
+ }
+
+ return sbin;
+}
+
+/*
+ * Error handling
+ */
+
+#undef sieve_generator_error
+void sieve_generator_error(struct sieve_generator *gentr,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(gentr->genenv.script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(gentr->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_generator_warning
+void sieve_generator_warning(struct sieve_generator *gentr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(gentr->genenv.script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(gentr->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_generator_critical
+void sieve_generator_critical(struct sieve_generator *gentr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(gentr->genenv.script, source_line);
+
+ va_start(args, fmt);
+ sieve_criticalv(gentr->genenv.svinst, gentr->ehandler, &params,
+ "Code generation failed", fmt, args);
+ va_end(args);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-generator.h b/pigeonhole/src/lib-sieve/sieve-generator.h
new file mode 100644
index 0000000..fab2023
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-generator.h
@@ -0,0 +1,121 @@
+#ifndef SIEVE_GENERATOR_H
+#define SIEVE_GENERATOR_H
+
+#include "sieve-common.h"
+
+/*
+ * Code generator
+ */
+
+struct sieve_generator;
+
+struct sieve_codegen_env {
+ struct sieve_generator *gentr;
+
+ struct sieve_instance *svinst;
+ enum sieve_compile_flags flags;
+
+ struct sieve_script *script;
+ struct sieve_ast *ast;
+
+ struct sieve_binary *sbin;
+ struct sieve_binary_block *sblock;
+};
+
+struct sieve_generator *
+sieve_generator_create(struct sieve_ast *ast,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags);
+void sieve_generator_free(struct sieve_generator **generator);
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *
+sieve_generator_error_handler(struct sieve_generator *gentr);
+pool_t sieve_generator_pool(struct sieve_generator *gentr);
+struct sieve_script *sieve_generator_script(struct sieve_generator *gentr);
+struct sieve_binary *sieve_generator_get_binary(struct sieve_generator *gentr);
+struct sieve_binary_block *
+sieve_generator_get_block(struct sieve_generator *gentr);
+
+/*
+ * Extension support
+ */
+
+void sieve_generator_extension_set_context(struct sieve_generator *gentr,
+ const struct sieve_extension *ext,
+ void *context);
+const void *
+sieve_generator_extension_get_context(struct sieve_generator *gentr,
+ const struct sieve_extension *ext);
+
+/*
+ * Jump list
+ */
+
+struct sieve_jumplist {
+ pool_t pool;
+ struct sieve_binary_block *block;
+ ARRAY(sieve_size_t) jumps;
+};
+
+struct sieve_jumplist *
+sieve_jumplist_create(pool_t pool, struct sieve_binary_block *sblock);
+void sieve_jumplist_init_temp(struct sieve_jumplist *jlist,
+ struct sieve_binary_block *sblock);
+void sieve_jumplist_reset(struct sieve_jumplist *jlist);
+void sieve_jumplist_add(struct sieve_jumplist *jlist, sieve_size_t jump);
+void sieve_jumplist_resolve(struct sieve_jumplist *jlist);
+
+/*
+ * Code generation API
+ */
+
+bool sieve_generate_argument(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+bool sieve_generate_arguments(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument **last_arg_r);
+bool sieve_generate_argument_parameters(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg);
+
+bool sieve_generate_block(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *block);
+bool sieve_generate_test(const struct sieve_codegen_env *cgenv,
+ struct sieve_ast_node *tst_node,
+ struct sieve_jumplist *jlist, bool jump_true);
+struct sieve_binary *
+sieve_generator_run(struct sieve_generator *gentr,
+ struct sieve_binary_block **sblock_r);
+
+/*
+ * Error handling
+ */
+
+void sieve_generator_error(struct sieve_generator *gentr,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_generator_error(gentr, ...) \
+ sieve_generator_error(gentr, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_generator_warning(struct sieve_generator *gentr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_generator_warning(gentr, ...) \
+ sieve_generator_warning(gentr, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_generator_critical(struct sieve_generator *gentr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_generator_critical(gentr, ...) \
+ sieve_generator_critical(gentr, __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
+
diff --git a/pigeonhole/src/lib-sieve/sieve-interpreter.c b/pigeonhole/src/lib-sieve/sieve-interpreter.c
new file mode 100644
index 0000000..274e142
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-interpreter.c
@@ -0,0 +1,1196 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "mempool.h"
+#include "array.h"
+#include "hash.h"
+#include "cpu-limit.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-comparators.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-interpreter.h"
+
+#include <string.h>
+
+static struct event_category event_category_sieve_runtime = {
+ .parent = &event_category_sieve,
+ .name = "sieve-runtime",
+};
+
+/*
+ * Interpreter extension
+ */
+
+struct sieve_interpreter_extension_reg {
+ const struct sieve_interpreter_extension *intext;
+ const struct sieve_extension *ext;
+
+ void *context;
+
+ bool deferred:1;
+ bool started:1;
+};
+
+/*
+ * Code loop
+ */
+
+struct sieve_interpreter_loop {
+ unsigned int level;
+ sieve_size_t begin, end;
+ const struct sieve_extension_def *ext_def;
+ pool_t pool;
+ void *context;
+};
+
+/*
+ * Interpreter
+ */
+
+struct sieve_interpreter {
+ pool_t pool;
+ struct sieve_interpreter *parent;
+
+ /* Runtime data for extensions */
+ ARRAY(struct sieve_interpreter_extension_reg) extensions;
+
+ sieve_size_t reset_vector;
+
+ /* Execution status */
+ sieve_size_t pc; /* Program counter */
+
+ /* Loop stack */
+ ARRAY(struct sieve_interpreter_loop) loop_stack;
+ sieve_size_t loop_limit;
+ unsigned int parent_loop_level;
+
+ /* Runtime environment */
+ struct sieve_runtime_env runenv;
+ struct sieve_runtime_trace trace;
+ struct sieve_resource_usage rusage;
+
+ /* Current operation */
+ struct sieve_operation oprtn;
+
+ /* Location information */
+ struct sieve_binary_debug_reader *dreader;
+ unsigned int command_line;
+
+ bool running:1; /* Interpreter is running
+ (may be interrupted) */
+ bool interrupted:1; /* Interpreter interrupt requested */
+ bool test_result:1; /* Result of previous test command */
+};
+
+static struct sieve_interpreter *
+_sieve_interpreter_create(struct sieve_binary *sbin,
+ struct sieve_binary_block *sblock,
+ struct sieve_script *script,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler) ATTR_NULL(3, 4)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ unsigned int i, ext_count;
+ struct sieve_interpreter *interp;
+ pool_t pool;
+ struct sieve_instance *svinst;
+ const struct sieve_extension *const *ext_preloaded;
+ unsigned int debug_block_id;
+ sieve_size_t *address;
+ bool success = TRUE;
+
+ pool = pool_alloconly_create("sieve_interpreter", 4096);
+ interp = p_new(pool, struct sieve_interpreter, 1);
+ interp->parent = parent;
+ interp->pool = pool;
+
+ interp->runenv.ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ interp->runenv.exec_env = eenv;
+ interp->runenv.interp = interp;
+ interp->runenv.oprtn = &interp->oprtn;
+ interp->runenv.sbin = sbin;
+ interp->runenv.sblock = sblock;
+ sieve_binary_ref(sbin);
+
+ interp->runenv.event = event_create(eenv->event);
+ event_add_category(interp->runenv.event, &event_category_sieve_runtime);
+ event_add_str(interp->runenv.event, "script_name",
+ sieve_binary_script_name(sbin));
+ event_add_str(interp->runenv.event, "script_location",
+ sieve_binary_script_location(sbin));
+ event_add_str(interp->runenv.event, "binary_path",
+ sieve_binary_path(sbin));
+
+ svinst = sieve_binary_svinst(sbin);
+
+ if (senv->trace_log != NULL) {
+ interp->trace.log = senv->trace_log;
+ interp->trace.config = senv->trace_config;
+ interp->trace.indent = 0;
+ interp->runenv.trace = &interp->trace;
+ }
+
+ if (script == NULL)
+ interp->runenv.script = sieve_binary_script(sbin);
+ else
+ interp->runenv.script = script;
+
+ interp->runenv.pc = 0;
+ address = &(interp->runenv.pc);
+
+ sieve_runtime_trace_begin(&(interp->runenv));
+
+ p_array_init(&interp->extensions, pool,
+ sieve_extensions_get_count(svinst));
+
+ interp->parent_loop_level = 0;
+ if (parent != NULL && array_is_created(&parent->loop_stack)) {
+ interp->parent_loop_level = parent->parent_loop_level +
+ array_count(&parent->loop_stack);
+ }
+
+ /* Pre-load core language features implemented as 'extensions' */
+ ext_preloaded = sieve_extensions_get_preloaded(svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension_def *ext_def =
+ ext_preloaded[i]->def;
+
+ if (ext_def != NULL && ext_def->interpreter_load != NULL) {
+ (void)ext_def->interpreter_load(ext_preloaded[i],
+ &interp->runenv,
+ address);
+ }
+ }
+
+ /* Load debug block */
+ if (sieve_binary_read_unsigned(sblock, address, &debug_block_id)) {
+ struct sieve_binary_block *debug_block =
+ sieve_binary_block_get(sbin, debug_block_id);
+
+ if (debug_block == NULL) {
+ sieve_runtime_trace_error(&interp->runenv,
+ "invalid id for debug block");
+ success = FALSE;
+ } else {
+ /* Initialize debug reader */
+ interp->dreader =
+ sieve_binary_debug_reader_init(debug_block);
+ }
+ }
+
+ /* Load other extensions listed in code */
+ if (success && sieve_binary_read_unsigned(sblock, address,
+ &ext_count)) {
+
+ for (i = 0; i < ext_count; i++) {
+ unsigned int code = 0, deferred;
+ struct sieve_interpreter_extension_reg *reg;
+ const struct sieve_extension *ext;
+
+ if (!sieve_binary_read_extension(sblock, address,
+ &code, &ext) ||
+ !sieve_binary_read_byte(sblock, address,
+ &deferred)) {
+ success = FALSE;
+ break;
+ }
+
+ if (deferred > 0 && ext->id >= 0) {
+ reg = array_idx_get_space(
+ &interp->extensions,
+ (unsigned int)ext->id);
+ reg->deferred = TRUE;
+ }
+
+ if (ext->def != NULL) {
+ if (ext->global &&
+ (eenv->flags & SIEVE_EXECUTE_FLAG_NOGLOBAL) != 0) {
+ sieve_runtime_error(&interp->runenv, NULL,
+ "failed to enable extension `%s': "
+ "its use is restricted to global scripts",
+ sieve_extension_name(ext));
+ success = FALSE;
+ break;
+ }
+
+ if (ext->def->interpreter_load != NULL &&
+ !ext->def->interpreter_load(ext, &interp->runenv,
+ address)) {
+ success = FALSE;
+ break;
+ }
+ }
+ }
+ } else {
+ success = FALSE;
+ }
+
+ if (!success) {
+ sieve_interpreter_free(&interp);
+ interp = NULL;
+ } else {
+ interp->reset_vector = *address;
+ }
+
+ return interp;
+}
+
+struct sieve_interpreter *
+sieve_interpreter_create(struct sieve_binary *sbin,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler)
+{
+ struct sieve_binary_block *sblock;
+
+ if ((sblock = sieve_binary_block_get(
+ sbin, SBIN_SYSBLOCK_MAIN_PROGRAM)) == NULL)
+ return NULL;
+
+ return _sieve_interpreter_create(sbin, sblock, NULL, parent, eenv,
+ ehandler);
+}
+
+struct sieve_interpreter *
+sieve_interpreter_create_for_block(struct sieve_binary_block *sblock,
+ struct sieve_script *script,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler)
+{
+ if (sblock == NULL) return NULL;
+
+ return _sieve_interpreter_create(sieve_binary_block_get_binary(sblock),
+ sblock, script, parent, eenv,
+ ehandler);
+}
+
+void sieve_interpreter_free(struct sieve_interpreter **_interp)
+{
+ struct sieve_interpreter *interp = *_interp;
+ struct sieve_runtime_env *renv = &interp->runenv;
+ const struct sieve_interpreter_extension_reg *eregs;
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (interp->running) {
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_finished")->
+ add_str("error", "Aborted");
+ e_debug(e->event(), "Aborted running script `%s'",
+ sieve_binary_source(interp->runenv.sbin));
+
+ interp->running = FALSE;
+ }
+
+ if (array_is_created(&interp->loop_stack)) {
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = 0; i < count; i++)
+ pool_unref(&loops[i].pool);
+ }
+
+ interp->trace.indent = 0;
+ sieve_runtime_trace_end(renv);
+
+ /* Signal registered extensions that the interpreter is being destroyed */
+ eregs = array_get(&interp->extensions, &count);
+ for (i = 0; i < count; i++) {
+ if (eregs[i].intext != NULL && eregs[i].intext->free != NULL) {
+ eregs[i].intext->free(eregs[i].ext, interp,
+ eregs[i].context);
+ }
+ }
+
+ sieve_binary_debug_reader_deinit(&interp->dreader);
+ sieve_binary_unref(&renv->sbin);
+ sieve_error_handler_unref(&renv->ehandler);
+ event_unref(&renv->event);
+
+ pool_unref(&interp->pool);
+ *_interp = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_interpreter_pool(struct sieve_interpreter *interp)
+{
+ return interp->pool;
+}
+
+struct sieve_interpreter *
+sieve_interpreter_get_parent(struct sieve_interpreter *interp)
+{
+ return interp->parent;
+}
+
+struct sieve_script *sieve_interpreter_script(struct sieve_interpreter *interp)
+{
+ return interp->runenv.script;
+}
+
+struct sieve_error_handler *
+sieve_interpreter_get_error_handler(struct sieve_interpreter *interp)
+{
+ return interp->runenv.ehandler;
+}
+
+struct sieve_instance *
+sieve_interpreter_svinst(struct sieve_interpreter *interp)
+{
+ return interp->runenv.exec_env->svinst;
+}
+
+/* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+void sieve_interpreter_set_result(struct sieve_interpreter *interp,
+ struct sieve_result *result)
+{
+ sieve_result_unref(&interp->runenv.result);
+ interp->runenv.result = result;
+ interp->runenv.msgctx = sieve_result_get_message_context(result);
+ sieve_result_ref(result);
+}
+
+/*
+ * Source location
+ */
+
+unsigned int
+sieve_runtime_get_source_location(const struct sieve_runtime_env *renv,
+ sieve_size_t code_address)
+{
+ struct sieve_interpreter *interp = renv->interp;
+
+ if (interp->dreader == NULL)
+ return 0;
+
+ if (interp->command_line == 0) {
+ interp->command_line =
+ sieve_binary_debug_read_line(interp->dreader,
+ renv->oprtn->address);
+ }
+
+ return sieve_binary_debug_read_line(interp->dreader, code_address);
+}
+
+unsigned int
+sieve_runtime_get_command_location(const struct sieve_runtime_env *renv)
+{
+ struct sieve_interpreter *interp = renv->interp;
+
+ if (interp->dreader == NULL)
+ return 0;
+
+ if (interp->command_line == 0) {
+ interp->command_line =
+ sieve_binary_debug_read_line(interp->dreader,
+ renv->oprtn->address);
+ }
+
+ return interp->command_line;
+}
+
+const char *
+sieve_runtime_get_full_command_location(const struct sieve_runtime_env *renv)
+{
+ return sieve_error_script_location(
+ renv->script, sieve_runtime_get_command_location(renv));
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_interpreter_extension_register(
+ struct sieve_interpreter *interp, const struct sieve_extension *ext,
+ const struct sieve_interpreter_extension *intext, void *context)
+{
+ struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+ reg->intext = intext;
+ reg->ext = ext;
+ reg->context = context;
+}
+
+void sieve_interpreter_extension_set_context(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&interp->extensions, (unsigned int) ext->id);
+ reg->context = context;
+}
+
+void *sieve_interpreter_extension_get_context(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_interpreter_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&interp->extensions))
+ return NULL;
+
+ reg = array_idx(&interp->extensions, (unsigned int) ext->id);
+
+ return reg->context;
+}
+
+int sieve_interpreter_extension_start(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext)
+{
+ struct sieve_interpreter_extension_reg *reg;
+ int ret;
+
+ i_assert(ext->id >= 0);
+
+ if (ext->id >= (int) array_count(&interp->extensions))
+ return SIEVE_EXEC_OK;
+
+ reg = array_idx_modifiable(&interp->extensions, (unsigned int)ext->id);
+
+ if (!reg->deferred)
+ return SIEVE_EXEC_OK;
+ reg->deferred = FALSE;
+ reg->started = TRUE;
+
+ if (reg->intext != NULL && reg->intext->run != NULL &&
+ (ret = reg->intext->run(ext, &interp->runenv,
+ reg->context, TRUE)) <= 0)
+ return ret;
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Loop handling
+ */
+
+int sieve_interpreter_loop_start(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def,
+ struct sieve_interpreter_loop **loop_r)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loop;
+
+ i_assert(loop_end > interp->runenv.pc);
+
+ /* Check supplied end offset */
+ if (loop_end > sieve_binary_block_get_size(renv->sblock)) {
+ sieve_runtime_trace_error(renv, "loop end offset out of range");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int line =
+ sieve_runtime_get_source_location(renv, loop_end);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "loop ends at line %d [%08llx]",
+ line,
+ (long long unsigned int) loop_end);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "loop ends at line %d", line);
+ }
+ }
+
+ /* Check loop nesting limit */
+ if (!array_is_created(&interp->loop_stack))
+ p_array_init(&interp->loop_stack, interp->pool, 8);
+ if ((interp->parent_loop_level +
+ array_count(&interp->loop_stack)) >= SIEVE_MAX_LOOP_DEPTH) {
+ /* Should normally be caught at compile time */
+ sieve_runtime_error(renv, NULL,
+ "new program loop exceeds "
+ "the nesting limit (<= %u levels)",
+ SIEVE_MAX_LOOP_DEPTH);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Create new loop */
+ loop = array_append_space(&interp->loop_stack);
+ loop->level = array_count(&interp->loop_stack)-1;
+ loop->ext_def = ext_def;
+ loop->begin = interp->runenv.pc;
+ loop->end = loop_end;
+ loop->pool = pool_alloconly_create("sieve_interpreter", 128);
+
+ /* Set new loop limit */
+ interp->loop_limit = loop_end;
+
+ *loop_r = loop;
+ return SIEVE_EXEC_OK;
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return NULL;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = count; i > 0; i--) {
+ /* We're really making sure our loop matches */
+ if (loops[i-1].end == loop_end &&
+ loops[i-1].ext_def == ext_def)
+ return &loops[i-1];
+ }
+ return NULL;
+}
+
+int sieve_interpreter_loop_next(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ sieve_size_t loop_begin)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loops;
+ unsigned int count;
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int line =
+ sieve_runtime_get_source_location(renv, loop_begin);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "looping back to line %d [%08llx]",
+ line,
+ (long long unsigned int) loop_begin);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "looping back to line %d", line);
+ }
+ }
+
+ /* Check the code for corruption */
+ if (loop->begin != loop_begin) {
+ sieve_runtime_trace_error(renv, "loop begin offset invalid");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Check invariants */
+ i_assert(array_is_created(&interp->loop_stack));
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(&loops[count-1] == loop);
+
+ /* Return to beginning */
+ interp->runenv.pc = loop_begin;
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_interpreter_loop_break(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ struct sieve_interpreter_loop *loops;
+ sieve_size_t loop_end = loop->end;
+ unsigned int count, i;
+
+ /* Find the loop */
+ i_assert(array_is_created(&interp->loop_stack));
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(count > 0);
+
+ i = count;
+ do {
+ pool_unref(&loops[i-1].pool);
+ i--;
+ } while (i > 0 && &loops[i] != loop);
+ i_assert(&loops[i] == loop);
+
+ /* Set new loop limit */
+ if (i > 0)
+ interp->loop_limit = loops[i].end;
+ else
+ interp->loop_limit = 0;
+
+ /* Delete it and all deeper loops */
+ array_delete(&interp->loop_stack, i, count - i);
+
+ /* Trace */
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int jmp_line =
+ sieve_runtime_get_source_location(renv, loop_end);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0,
+ "exiting loops at line %d [%08llx]",
+ jmp_line,
+ (long long unsigned int) loop_end);
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "exiting loops at line %d",
+ jmp_line);
+ }
+ }
+
+ /* Exit loop */
+ interp->runenv.pc = loop->end;
+ return SIEVE_EXEC_OK;
+}
+
+static int
+sieve_interpreter_loop_break_out(struct sieve_interpreter *interp,
+ sieve_size_t target)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return SIEVE_EXEC_OK;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ for (i = count; i > 0; i--) {
+ /* We're really making sure our loop matches */
+ if (loops[i-1].end > target)
+ break;
+ }
+ if (i == count)
+ return SIEVE_EXEC_OK;
+
+ return sieve_interpreter_loop_break(interp, &loops[i]);
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_local(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *loops;
+ unsigned int count, i;
+
+ if (!array_is_created(&interp->loop_stack))
+ return NULL;
+
+ loops = array_get_modifiable(&interp->loop_stack, &count);
+ i_assert(loop == NULL || loop->level < count);
+
+ for (i = (loop == NULL ? count : loop->level); i > 0; i--) {
+ if (ext_def == NULL || loops[i-1].ext_def == ext_def)
+ return &loops[i-1];
+ }
+ return NULL;
+}
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_global(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+{
+ struct sieve_interpreter_loop *result;
+
+ while (interp != NULL) {
+ result = sieve_interpreter_loop_get_local(interp, loop,
+ ext_def);
+ if (result != NULL)
+ return result;
+ interp = interp->parent;
+ }
+ return NULL;
+}
+
+pool_t sieve_interpreter_loop_get_pool(struct sieve_interpreter_loop *loop)
+{
+ return loop->pool;
+}
+
+void *sieve_interpreter_loop_get_context(struct sieve_interpreter_loop *loop)
+{
+ return loop->context;
+}
+
+void sieve_interpreter_loop_set_context(struct sieve_interpreter_loop *loop,
+ void *context)
+{
+ loop->context = context;
+}
+
+/*
+ * Program flow
+ */
+
+void sieve_interpreter_reset(struct sieve_interpreter *interp)
+{
+ interp->runenv.pc = interp->reset_vector;
+ interp->interrupted = FALSE;
+ interp->test_result = FALSE;
+ interp->runenv.result = NULL;
+}
+
+void sieve_interpreter_interrupt(struct sieve_interpreter *interp)
+{
+ interp->interrupted = TRUE;
+}
+
+sieve_size_t sieve_interpreter_program_counter(struct sieve_interpreter *interp)
+{
+ return interp->runenv.pc;
+}
+
+static int
+sieve_interpreter_check_program_jump(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t loop_limit = (break_loops ? 0 : interp->loop_limit);
+
+ if (jmp_target == 0 ||
+ jmp_target > sieve_binary_block_get_size(renv->sblock) ||
+ (loop_limit > 0 && jmp_target >= loop_limit)) {
+ if (interp->loop_limit != 0) {
+ sieve_runtime_trace_error(
+ renv, "jump target crosses loop boundary");
+ } else {
+ sieve_runtime_trace_error(
+ renv, "jump target out of range");
+ }
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+static int
+sieve_interpreter_do_program_jump(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t *address = &(interp->runenv.pc);
+ int ret;
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ unsigned int jmp_line =
+ sieve_runtime_get_source_location(renv, jmp_target);
+
+ if (sieve_runtime_trace_hasflag(renv, SIEVE_TRFLG_ADDRESSES)) {
+ sieve_runtime_trace(renv, 0, "jumping to line %d [%08llx]",
+ jmp_line,
+ (long long unsigned int)jmp_target);
+ } else {
+ sieve_runtime_trace(renv, 0, "jumping to line %d",
+ jmp_line);
+ }
+ }
+
+ if (break_loops &&
+ (ret = sieve_interpreter_loop_break_out(interp,
+ jmp_target)) <= 0)
+ return ret;
+
+ *address = jmp_target;
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_interpreter_program_jump_to(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target,
+ bool break_loops)
+{
+ int ret;
+
+ ret = sieve_interpreter_check_program_jump(interp, jmp_target,
+ break_loops);
+ if (ret <= 0)
+ return ret;
+
+ return sieve_interpreter_do_program_jump(
+ interp, jmp_target, break_loops);
+}
+
+int sieve_interpreter_program_jump(struct sieve_interpreter *interp,
+ bool jump, bool break_loops)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ sieve_size_t *address = &(interp->runenv.pc);
+ sieve_size_t jmp_start = *address, jmp_target;
+ sieve_offset_t jmp_offset;
+ int ret;
+
+ if (!sieve_binary_read_offset(renv->sblock, address, &jmp_offset)) {
+ sieve_runtime_trace_error(renv, "invalid jump offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ jmp_target = jmp_start + jmp_offset;
+
+ ret = sieve_interpreter_check_program_jump(interp, jmp_target,
+ break_loops);
+ if (ret <= 0)
+ return ret;
+
+ if (!jump) {
+ sieve_runtime_trace(renv, 0, "not jumping");
+ return SIEVE_EXEC_OK;
+ }
+
+ return sieve_interpreter_do_program_jump(
+ interp, jmp_target, break_loops);
+}
+
+/*
+ * Test results
+ */
+
+void sieve_interpreter_set_test_result(struct sieve_interpreter *interp,
+ bool result)
+{
+ interp->test_result = result;
+}
+
+bool sieve_interpreter_get_test_result(struct sieve_interpreter *interp)
+{
+ return interp->test_result;
+}
+
+/*
+ * Code execute
+ */
+
+static int sieve_interpreter_operation_execute(struct sieve_interpreter *interp)
+{
+ struct sieve_operation *oprtn = &(interp->oprtn);
+ sieve_size_t *address = &(interp->runenv.pc);
+
+ sieve_runtime_trace_toplevel(&interp->runenv);
+
+ /* Read the operation */
+ if (sieve_operation_read(interp->runenv.sblock, address, oprtn)) {
+ const struct sieve_operation_def *op = oprtn->def;
+ int result = SIEVE_EXEC_OK;
+
+ /* Reset cached command location */
+ interp->command_line = 0;
+
+ /* Execute the operation */
+ if (op->execute != NULL) { /* Noop ? */
+ T_BEGIN {
+ result = op->execute(&(interp->runenv),
+ address);
+ } T_END;
+ } else {
+ sieve_runtime_trace(&interp->runenv,
+ SIEVE_TRLVL_COMMANDS,
+ "OP: %s (NOOP)",
+ sieve_operation_mnemonic(oprtn));
+ }
+
+ return result;
+ }
+
+ /* Binary corrupt */
+ sieve_runtime_trace_error(&interp->runenv,
+ "Encountered invalid operation");
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+int sieve_interpreter_continue(struct sieve_interpreter *interp,
+ bool *interrupted)
+{
+ const struct sieve_runtime_env *renv = &interp->runenv;
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct cpu_limit *climit = NULL;
+ sieve_size_t *address = &(interp->runenv.pc);
+ struct sieve_instance *svinst = eenv->svinst;
+ struct sieve_exec_status *exec_status = eenv->exec_status;
+ struct sieve_resource_usage rusage;
+ int ret = SIEVE_EXEC_OK;
+
+ sieve_result_ref(renv->result);
+ interp->interrupted = FALSE;
+
+ if (interrupted != NULL)
+ *interrupted = FALSE;
+
+ if (svinst->max_cpu_time_secs > 0) {
+ climit = cpu_limit_init(svinst->max_cpu_time_secs,
+ CPU_LIMIT_TYPE_USER);
+ }
+
+ while (ret == SIEVE_EXEC_OK && !interp->interrupted &&
+ *address < sieve_binary_block_get_size(renv->sblock)) {
+ if (climit != NULL && cpu_limit_exceeded(climit)) {
+ sieve_runtime_error(
+ renv, NULL,
+ "execution exceeded CPU time limit");
+ ret = SIEVE_EXEC_RESOURCE_LIMIT;
+ break;
+ }
+ if (interp->loop_limit != 0 && *address > interp->loop_limit) {
+ sieve_runtime_trace_error(
+ renv, "program crossed loop boundary");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ break;
+ }
+
+ ret = sieve_interpreter_operation_execute(interp);
+ }
+
+ if (climit != NULL) {
+ sieve_resource_usage_init(&rusage);
+ rusage.cpu_time_msecs =
+ cpu_limit_get_usage_msecs(climit, CPU_LIMIT_TYPE_USER);
+ sieve_resource_usage_add(&interp->rusage, &rusage);
+
+ cpu_limit_deinit(&climit);
+ }
+
+ if (ret != SIEVE_EXEC_OK) {
+ sieve_runtime_trace(&interp->runenv, SIEVE_TRLVL_NONE,
+ "[[EXECUTION ABORTED]]");
+ }
+
+ if (interrupted != NULL)
+ *interrupted = interp->interrupted;
+
+ if (!interp->interrupted) {
+ exec_status->resource_usage = interp->rusage;
+
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_finished");
+ switch (ret) {
+ case SIEVE_EXEC_OK:
+ break;
+ case SIEVE_EXEC_FAILURE:
+ e->add_str("error", "Failed");
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ e->add_str("error", "Failed temporarily");
+ break;
+ case SIEVE_EXEC_BIN_CORRUPT:
+ e->add_str("error", "Binary corrupt");
+ break;
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ e->add_str("error", "Resource limit exceeded");
+ break;
+ case SIEVE_EXEC_KEEP_FAILED:
+ /* Not supposed to occur at runtime */
+ i_unreached();
+ }
+ e_debug(e->event(), "Finished running script `%s' "
+ "(status=%s, resource usage: %s)",
+ sieve_binary_source(interp->runenv.sbin),
+ sieve_execution_exitcode_to_str(ret),
+ sieve_resource_usage_get_summary(&interp->rusage));
+ interp->running = FALSE;
+ }
+
+ sieve_result_unref(&interp->runenv.result);
+ return ret;
+}
+
+int sieve_interpreter_start(struct sieve_interpreter *interp,
+ struct sieve_result *result, bool *interrupted)
+{
+ struct sieve_interpreter_extension_reg *eregs;
+ unsigned int ext_count, i;
+ int ret;
+
+ struct event_passthrough *e =
+ event_create_passthrough(interp->runenv.event)->
+ set_name("sieve_runtime_script_started");
+ e_debug(e->event(), "Started running script `%s'",
+ sieve_binary_source(interp->runenv.sbin));
+
+ interp->running = TRUE;
+ interp->runenv.result = result;
+ interp->runenv.msgctx = sieve_result_get_message_context(result);
+
+ sieve_resource_usage_init(&interp->rusage);
+
+ /* Signal registered extensions that the interpreter is being run */
+ eregs = array_get_modifiable(&interp->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (!eregs[i].deferred) {
+ eregs[i].started = TRUE;
+ if (eregs[i].intext != NULL &&
+ eregs[i].intext->run != NULL &&
+ (ret = eregs[i].intext->run(
+ eregs[i].ext, &interp->runenv,
+ eregs[i].context, FALSE)) <= 0)
+ return ret;
+ }
+ }
+
+ return sieve_interpreter_continue(interp, interrupted);
+}
+
+int sieve_interpreter_run(struct sieve_interpreter *interp,
+ struct sieve_result *result)
+{
+ sieve_interpreter_reset(interp);
+
+ return sieve_interpreter_start(interp, result, NULL);
+}
+
+/*
+ * Error handling
+ */
+
+static inline void ATTR_FORMAT(3, 0)
+sieve_runtime_logv(const struct sieve_runtime_env *renv,
+ const struct sieve_error_params *params,
+ const char *fmt, va_list args)
+{
+ struct sieve_error_params new_params = *params;
+
+ new_params.event = renv->event;
+ T_BEGIN {
+ if (new_params.location == NULL) {
+ new_params.location =
+ sieve_runtime_get_full_command_location(renv);
+ }
+
+ sieve_logv(renv->ehandler, params, fmt, args);
+ } T_END;
+}
+
+#undef sieve_runtime_error
+void sieve_runtime_error(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_warning
+void sieve_runtime_warning(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_log
+void sieve_runtime_log(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_INFO,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_runtime_logv(renv, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_runtime_critical
+void sieve_runtime_critical(const struct sieve_runtime_env *renv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *user_prefix,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ .location = location,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ params.event = renv->event;
+ T_BEGIN {
+ if (params.location == NULL) {
+ params.location =
+ sieve_runtime_get_full_command_location(renv);
+ }
+
+ sieve_criticalv(eenv->svinst, renv->ehandler, &params,
+ user_prefix, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_runtime_mail_error
+int sieve_runtime_mail_error(const struct sieve_runtime_env *renv,
+ struct mail *mail,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const char *error_msg, *user_prefix;
+ va_list args;
+
+ error_msg = mailbox_get_last_internal_error(mail->box, NULL);
+
+ va_start(args, fmt);
+ user_prefix = t_strdup_vprintf(fmt, args);
+ sieve_runtime_critical(renv, csrc_filename, csrc_linenum,
+ NULL, user_prefix, "%s: %s",
+ user_prefix, error_msg);
+ va_end(args);
+
+ return SIEVE_EXEC_TEMP_FAILURE;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-interpreter.h b/pigeonhole/src/lib-sieve/sieve-interpreter.h
new file mode 100644
index 0000000..ec38029
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-interpreter.h
@@ -0,0 +1,204 @@
+#ifndef SIEVE_INTERPRETER_H
+#define SIEVE_INTERPRETER_H
+
+#include "lib.h"
+#include "array.h"
+#include "buffer.h"
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+
+/*
+ * Interpreter
+ */
+
+struct sieve_interpreter *
+sieve_interpreter_create(struct sieve_binary *sbin,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler) ATTR_NULL(2);
+struct sieve_interpreter *
+sieve_interpreter_create_for_block(struct sieve_binary_block *sblock,
+ struct sieve_script *script,
+ struct sieve_interpreter *parent,
+ const struct sieve_execute_env *eenv,
+ struct sieve_error_handler *ehandler)
+ ATTR_NULL(3);
+void sieve_interpreter_free(struct sieve_interpreter **_interp);
+
+/*
+ * Accessors
+ */
+
+pool_t sieve_interpreter_pool(struct sieve_interpreter *interp);
+struct sieve_interpreter *
+sieve_interpreter_get_parent(struct sieve_interpreter *interp);
+struct sieve_script *sieve_interpreter_script(struct sieve_interpreter *interp);
+struct sieve_error_handler *
+sieve_interpreter_get_error_handler(struct sieve_interpreter *interp);
+struct sieve_instance *
+sieve_interpreter_svinst(struct sieve_interpreter *interp);
+
+/* Do not use this function for normal sieve extensions. This is intended for
+ * the testsuite only.
+ */
+void sieve_interpreter_set_result(struct sieve_interpreter *interp,
+ struct sieve_result *result);
+
+/*
+ * Loop handling
+ */
+
+struct sieve_interpreter_loop;
+
+int sieve_interpreter_loop_start(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def,
+ struct sieve_interpreter_loop **loop_r);
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get(struct sieve_interpreter *interp,
+ sieve_size_t loop_end,
+ const struct sieve_extension_def *ext_def);
+int sieve_interpreter_loop_next(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ sieve_size_t loop_begin);
+int sieve_interpreter_loop_break(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop);
+
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_local(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+ ATTR_NULL(2, 3);
+struct sieve_interpreter_loop *
+sieve_interpreter_loop_get_global(struct sieve_interpreter *interp,
+ struct sieve_interpreter_loop *loop,
+ const struct sieve_extension_def *ext_def)
+ ATTR_NULL(2, 3);
+
+pool_t sieve_interpreter_loop_get_pool(struct sieve_interpreter_loop *loop)
+ ATTR_PURE;
+void *sieve_interpreter_loop_get_context(struct sieve_interpreter_loop *loop)
+ ATTR_PURE;
+void sieve_interpreter_loop_set_context(struct sieve_interpreter_loop *loop,
+ void *context);
+
+/*
+ * Program flow
+ */
+
+void sieve_interpreter_reset(struct sieve_interpreter *interp);
+void sieve_interpreter_interrupt(struct sieve_interpreter *interp);
+sieve_size_t
+sieve_interpreter_program_counter(struct sieve_interpreter *interp);
+
+int sieve_interpreter_program_jump_to(struct sieve_interpreter *interp,
+ sieve_size_t jmp_target,
+ bool break_loops);
+int sieve_interpreter_program_jump(struct sieve_interpreter *interp, bool jump,
+ bool break_loops);
+
+/*
+ * Test results
+ */
+
+void sieve_interpreter_set_test_result(struct sieve_interpreter *interp,
+ bool result);
+bool sieve_interpreter_get_test_result(struct sieve_interpreter *interp);
+
+/*
+ * Source location
+ */
+
+unsigned int
+sieve_runtime_get_source_location(const struct sieve_runtime_env *renv,
+ sieve_size_t code_address);
+
+unsigned int
+sieve_runtime_get_command_location(const struct sieve_runtime_env *renv);
+const char *
+sieve_runtime_get_full_command_location(const struct sieve_runtime_env *renv);
+
+/*
+ * Extension support
+ */
+
+struct sieve_interpreter_extension {
+ const struct sieve_extension_def *ext_def;
+
+ int (*run)(const struct sieve_extension *ext,
+ const struct sieve_runtime_env *renv,
+ void *context, bool deferred);
+ void (*free)(const struct sieve_extension *ext,
+ struct sieve_interpreter *interp, void *context);
+};
+
+void sieve_interpreter_extension_register(
+ struct sieve_interpreter *interp, const struct sieve_extension *ext,
+ const struct sieve_interpreter_extension *intext, void *context);
+void sieve_interpreter_extension_set_context(
+ struct sieve_interpreter *interp, const struct sieve_extension *ext,
+ void *context);
+void *sieve_interpreter_extension_get_context(
+ struct sieve_interpreter *interp, const struct sieve_extension *ext);
+
+int sieve_interpreter_extension_start(struct sieve_interpreter *interp,
+ const struct sieve_extension *ext);
+
+/*
+ * Opcodes and operands
+ */
+
+int sieve_interpreter_handle_optional_operands(
+ const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_side_effects_list **list);
+
+/*
+ * Code execute
+ */
+
+int sieve_interpreter_continue(struct sieve_interpreter *interp,
+ bool *interrupted);
+int sieve_interpreter_start(struct sieve_interpreter *interp,
+ struct sieve_result *result, bool *interrupted);
+int sieve_interpreter_run(struct sieve_interpreter *interp,
+ struct sieve_result *result);
+
+/*
+ * Error handling
+ */
+
+void sieve_runtime_error(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_runtime_error(renv, ...) \
+ sieve_runtime_error(renv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_runtime_warning(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_runtime_warning(renv, ...) \
+ sieve_runtime_warning(renv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_runtime_log(const struct sieve_runtime_env *renv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *location, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_runtime_log(renv, ...) \
+ sieve_runtime_log(renv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_runtime_critical(const struct sieve_runtime_env *renv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *location, const char *user_prefix,
+ const char *fmt, ...) ATTR_FORMAT(6, 7);
+#define sieve_runtime_critical(renv, ...) \
+ sieve_runtime_critical(renv, __FILE__, __LINE__, __VA_ARGS__)
+int sieve_runtime_mail_error(const struct sieve_runtime_env *renv,
+ struct mail *mail,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...) ATTR_FORMAT(5, 6);
+#define sieve_runtime_mail_error(renv, mail, ...) \
+ sieve_runtime_mail_error(renv, mail, __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-lexer.c b/pigeonhole/src/lib-sieve/sieve-lexer.c
new file mode 100644
index 0000000..a6968a9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-lexer.c
@@ -0,0 +1,930 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+
+#include "sieve-lexer.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+
+/*
+ * Useful macros
+ */
+
+#define DIGIT_VAL(c) (c - '0')
+
+/*
+ * Lexer object
+ */
+
+struct sieve_lexical_scanner {
+ pool_t pool;
+ struct sieve_instance *svinst;
+
+ struct sieve_script *script;
+ struct istream *input;
+
+ struct sieve_error_handler *ehandler;
+
+ /* Currently scanned data */
+ const unsigned char *buffer;
+ size_t buffer_size;
+ size_t buffer_pos;
+
+ struct sieve_lexer lexer;
+
+ int current_line;
+};
+
+const struct sieve_lexer *
+sieve_lexer_create(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r)
+{
+ struct sieve_lexical_scanner *scanner;
+ struct sieve_instance *svinst = sieve_script_svinst(script);
+ struct istream *stream;
+ const struct stat *st;
+
+ /* Open script as stream */
+ if (sieve_script_get_stream(script, &stream, error_r) < 0)
+ return NULL;
+
+ /* Check script size */
+ if (i_stream_stat(stream, TRUE, &st) >= 0 && st->st_size > 0 &&
+ svinst->max_script_size > 0 &&
+ (uoff_t)st->st_size > svinst->max_script_size) {
+ sieve_error(ehandler, sieve_script_name(script),
+ "sieve script is too large (max %zu bytes)",
+ svinst->max_script_size);
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NOT_POSSIBLE;
+ return NULL;
+ }
+
+ scanner = i_new(struct sieve_lexical_scanner, 1);
+ scanner->lexer.scanner = scanner;
+
+ scanner->ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ scanner->input = stream;
+ i_stream_ref(scanner->input);
+
+ scanner->script = script;
+ sieve_script_ref(script);
+
+ scanner->buffer = NULL;
+ scanner->buffer_size = 0;
+ scanner->buffer_pos = 0;
+
+ scanner->lexer.token_type = STT_NONE;
+ scanner->lexer.token_str_value = str_new(default_pool, 256);
+ scanner->lexer.token_int_value = 0;
+ scanner->lexer.token_line = 1;
+
+ scanner->current_line = 1;
+
+ return &scanner->lexer;
+}
+
+void sieve_lexer_free(const struct sieve_lexer **_lexer)
+{
+ const struct sieve_lexer *lexer = *_lexer;
+ struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+ i_stream_unref(&scanner->input);
+ sieve_script_unref(&scanner->script);
+ sieve_error_handler_unref(&scanner->ehandler);
+ str_free(&scanner->lexer.token_str_value);
+
+ i_free(scanner);
+ *_lexer = NULL;
+}
+
+/*
+ * Internal error handling
+ */
+
+inline static void ATTR_FORMAT(4, 5)
+sieve_lexer_error(const struct sieve_lexer *lexer,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ struct sieve_lexical_scanner *scanner = lexer->scanner;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ T_BEGIN {
+ params.location =
+ sieve_error_script_location(scanner->script,
+ scanner->current_line);
+ sieve_logv(scanner->ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+#define sieve_lexer_error(lexer, ...) \
+ sieve_lexer_error(lexer, __FILE__, __LINE__, __VA_ARGS__)
+
+inline static void ATTR_FORMAT(4, 5)
+sieve_lexer_warning(const struct sieve_lexer *lexer,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ struct sieve_lexical_scanner *scanner = lexer->scanner;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ T_BEGIN {
+ params.location =
+ sieve_error_script_location(scanner->script,
+ scanner->current_line);
+ sieve_logv(scanner->ehandler, &params, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+#define sieve_lexer_warning(lexer, ...) \
+ sieve_lexer_warning(lexer, __FILE__, __LINE__, __VA_ARGS__)
+
+const char *sieve_lexer_token_description(const struct sieve_lexer *lexer)
+{
+ switch (lexer->token_type) {
+ case STT_NONE:
+ return "no token (bug)";
+ case STT_WHITESPACE:
+ return "whitespace (bug)";
+ case STT_EOF:
+ return "end of file";
+
+ case STT_NUMBER:
+ return "number";
+ case STT_IDENTIFIER:
+ return "identifier";
+ case STT_TAG:
+ return "tag";
+ case STT_STRING:
+ return "string";
+
+ case STT_RBRACKET:
+ return "')'";
+ case STT_LBRACKET:
+ return "'('";
+ case STT_RCURLY:
+ return "'}'";
+ case STT_LCURLY:
+ return "'{'";
+ case STT_RSQUARE:
+ return "']'";
+ case STT_LSQUARE:
+ return "'['";
+ case STT_SEMICOLON:
+ return "';'";
+ case STT_COMMA:
+ return "','";
+
+ case STT_SLASH:
+ return "'/'";
+ case STT_COLON:
+ return "':'";
+
+ case STT_GARBAGE:
+ return "unknown characters";
+ case STT_ERROR:
+ return "error token (bug)";
+ }
+
+ return "unknown token (bug)";
+}
+
+/*
+ * Debug
+ */
+
+void sieve_lexer_token_print(const struct sieve_lexer *lexer)
+{
+ switch (lexer->token_type) {
+ case STT_NONE:
+ printf("??NONE?? ");
+ break;
+ case STT_WHITESPACE:
+ printf("??WHITESPACE?? ");
+ break;
+ case STT_EOF:
+ printf("EOF\n");
+ break;
+
+ case STT_NUMBER:
+ printf("NUMBER ");
+ break;
+ case STT_IDENTIFIER:
+ printf("IDENTIFIER ");
+ break;
+ case STT_TAG:
+ printf("TAG ");
+ break;
+ case STT_STRING:
+ printf("STRING ");
+ break;
+
+ case STT_RBRACKET:
+ printf(") ");
+ break;
+ case STT_LBRACKET:
+ printf("( ");
+ break;
+ case STT_RCURLY:
+ printf("}\n");
+ break;
+ case STT_LCURLY:
+ printf("{\n");
+ break;
+ case STT_RSQUARE:
+ printf("] ");
+ break;
+ case STT_LSQUARE:
+ printf("[ ");
+ break;
+ case STT_SEMICOLON:
+ printf(";\n");
+ break;
+ case STT_COMMA:
+ printf(", ");
+ break;
+
+ case STT_SLASH:
+ printf("/ ");
+ break;
+ case STT_COLON:
+ printf(": ");
+ break;
+
+ case STT_GARBAGE:
+ printf(">>GARBAGE<<");
+ break;
+ case STT_ERROR:
+ printf(">>ERROR<<");
+ break;
+ default:
+ printf("UNKNOWN ");
+ break;
+ }
+}
+
+/*
+ * Lexical scanning
+ */
+
+static void sieve_lexer_shift(struct sieve_lexical_scanner *scanner)
+{
+ if (scanner->buffer_size > 0 &&
+ scanner->buffer[scanner->buffer_pos] == '\n')
+ scanner->current_line++;
+
+ if (scanner->buffer_size > 0 &&
+ scanner->buffer_pos + 1 < scanner->buffer_size)
+ scanner->buffer_pos++;
+ else {
+ if (scanner->buffer_size > 0)
+ i_stream_skip(scanner->input, scanner->buffer_size);
+
+ scanner->buffer = i_stream_get_data(scanner->input,
+ &scanner->buffer_size);
+
+ if (scanner->buffer_size == 0 &&
+ i_stream_read(scanner->input) > 0) {
+ scanner->buffer = i_stream_get_data(
+ scanner->input, &scanner->buffer_size);
+ }
+
+ scanner->buffer_pos = 0;
+ }
+}
+
+static inline int sieve_lexer_curchar(struct sieve_lexical_scanner *scanner)
+{
+ if (scanner->buffer_size == 0)
+ return -1;
+
+ return scanner->buffer[scanner->buffer_pos];
+}
+
+static inline const char *_char_sanitize(int ch)
+{
+ if (ch > 31 && ch < 127)
+ return t_strdup_printf("'%c'", ch);
+
+ return t_strdup_printf("0x%02x", ch);
+}
+
+static bool sieve_lexer_scan_number(struct sieve_lexical_scanner *scanner)
+{
+ struct sieve_lexer *lexer = &scanner->lexer;
+ uintmax_t value;
+ string_t *str;
+ bool overflow = FALSE;
+
+ str_truncate(lexer->token_str_value,0);
+ str = lexer->token_str_value;
+
+ while (i_isdigit(sieve_lexer_curchar(scanner))) {
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ sieve_lexer_shift(scanner);
+ }
+
+ if (str_to_uintmax(str_c(str), &value) < 0 ||
+ value > (sieve_number_t)-1) {
+ overflow = TRUE;
+ } else {
+ switch (sieve_lexer_curchar(scanner)) {
+ case 'k':
+ case 'K': /* Kilo */
+ if (value > (SIEVE_MAX_NUMBER >> 10))
+ overflow = TRUE;
+ else
+ value = value << 10;
+ sieve_lexer_shift(scanner);
+ break;
+ case 'm':
+ case 'M': /* Mega */
+ if (value > (SIEVE_MAX_NUMBER >> 20))
+ overflow = TRUE;
+ else
+ value = value << 20;
+ sieve_lexer_shift(scanner);
+ break;
+ case 'g':
+ case 'G': /* Giga */
+ if (value > (SIEVE_MAX_NUMBER >> 30))
+ overflow = TRUE;
+ else
+ value = value << 30;
+ sieve_lexer_shift(scanner);
+ break;
+ default:
+ /* Next token */
+ break;
+ }
+ }
+
+ /* Check for integer overflow */
+ if (overflow) {
+ sieve_lexer_error(lexer,
+ "number exceeds integer limits (max %llu)",
+ (long long) SIEVE_MAX_NUMBER);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ lexer->token_type = STT_NUMBER;
+ lexer->token_int_value = (sieve_number_t)value;
+ return TRUE;
+
+}
+
+static bool
+sieve_lexer_scan_hash_comment(struct sieve_lexical_scanner *scanner)
+{
+ struct sieve_lexer *lexer = &scanner->lexer;
+
+ while (sieve_lexer_curchar(scanner) != '\n') {
+ switch(sieve_lexer_curchar(scanner)) {
+ case -1:
+ if (!scanner->input->eof) {
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+ sieve_lexer_warning(lexer,
+ "no newline (CRLF) at end of hash comment at end of file");
+ lexer->token_type = STT_WHITESPACE;
+ return TRUE;
+ case '\0':
+ sieve_lexer_error(lexer,
+ "encountered NUL character in hash comment");
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Stray CR is ignored */
+ sieve_lexer_shift(scanner);
+ }
+
+ sieve_lexer_shift(scanner);
+
+ lexer->token_type = STT_WHITESPACE;
+ return TRUE;
+}
+
+/* sieve_lexer_scan_raw_token:
+ * Scans valid tokens and whitespace
+ */
+static bool
+sieve_lexer_scan_raw_token(struct sieve_lexical_scanner *scanner)
+{
+ struct sieve_lexer *lexer = &scanner->lexer;
+ string_t *str;
+ int ret;
+
+ /* Read first character */
+ if (lexer->token_type == STT_NONE) {
+ if ((ret = i_stream_read(scanner->input)) < 0) {
+ i_assert(ret != -2);
+ if (!scanner->input->eof) {
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+ }
+ sieve_lexer_shift(scanner);
+ }
+
+ lexer->token_line = scanner->current_line;
+
+ switch (sieve_lexer_curchar(scanner)) {
+
+ /* whitespace */
+
+ // hash-comment = ( "#" *CHAR-NOT-CRLF CRLF )
+ case '#':
+ sieve_lexer_shift(scanner);
+ return sieve_lexer_scan_hash_comment(scanner);
+
+ // bracket-comment = "/*" *(CHAR-NOT-STAR / ("*" CHAR-NOT-SLASH)) "*/"
+ // ;; No */ allowed inside a comment.
+ // ;; (No * is allowed unless it is the last character,
+ // ;; or unless it is followed by a character that isn't a
+ // ;; slash.)
+ case '/':
+ sieve_lexer_shift(scanner);
+
+ if (sieve_lexer_curchar(scanner) == '*') {
+ sieve_lexer_shift(scanner);
+
+ while (TRUE) {
+ switch (sieve_lexer_curchar(scanner)) {
+ case -1:
+ if (scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "end of file before end of bracket comment "
+ "('/* ... */') "
+ "started at line %d",
+ lexer->token_line);
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ case '*':
+ sieve_lexer_shift(scanner);
+
+ if (sieve_lexer_curchar(scanner) == '/') {
+ sieve_lexer_shift(scanner);
+
+ lexer->token_type = STT_WHITESPACE;
+ return TRUE;
+
+ } else if (sieve_lexer_curchar(scanner) == -1) {
+ sieve_lexer_error(lexer,
+ "end of file before end of bracket comment "
+ "('/* ... */') "
+ "started at line %d",
+ lexer->token_line);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+ break;
+ case '\0':
+ sieve_lexer_error(lexer,
+ "encountered NUL character in bracket comment");
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ default:
+ sieve_lexer_shift(scanner);
+ }
+ }
+
+ i_unreached();
+ return FALSE;
+ }
+
+ lexer->token_type = STT_SLASH;
+ return TRUE;
+
+ // comment = bracket-comment / hash-comment
+ // white-space = 1*(SP / CRLF / HTAB) / comment
+ case '\t':
+ case '\r':
+ case '\n':
+ case ' ':
+ sieve_lexer_shift(scanner);
+
+ while (sieve_lexer_curchar(scanner) == '\t' ||
+ sieve_lexer_curchar(scanner) == '\r' ||
+ sieve_lexer_curchar(scanner) == '\n' ||
+ sieve_lexer_curchar(scanner) == ' ') {
+
+ sieve_lexer_shift(scanner);
+ }
+
+ lexer->token_type = STT_WHITESPACE;
+ return TRUE;
+
+ /* quoted-string */
+ case '"':
+ sieve_lexer_shift(scanner);
+
+ str_truncate(lexer->token_str_value, 0);
+ str = lexer->token_str_value;
+
+ while (sieve_lexer_curchar(scanner) != '"') {
+ if (sieve_lexer_curchar(scanner) == '\\')
+ sieve_lexer_shift(scanner);
+
+ switch (sieve_lexer_curchar(scanner)) {
+
+ /* End of file */
+ case -1:
+ if (scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "end of file before end of quoted string "
+ "started at line %d", lexer->token_line);
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+
+ /* NUL character */
+ case '\0':
+ sieve_lexer_error(lexer,
+ "encountered NUL character in quoted string "
+ "started at line %d", lexer->token_line);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+
+ /* CR .. check for LF */
+ case '\r':
+ sieve_lexer_shift(scanner);
+
+ if (sieve_lexer_curchar(scanner) != '\n') {
+ sieve_lexer_error(lexer,
+ "found stray carriage-return (CR) character "
+ "in quoted string started at line %d",
+ lexer->token_line);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append(str, "\r\n");
+ break;
+
+ /* Loose LF is allowed (non-standard) and converted to CRLF */
+ case '\n':
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append(str, "\r\n");
+ break;
+
+ /* Other characters */
+ default:
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ }
+
+ sieve_lexer_shift(scanner);
+ }
+
+ sieve_lexer_shift(scanner);
+
+ if (str_len(str) > SIEVE_MAX_STRING_LEN) {
+ sieve_lexer_error(lexer,
+ "quoted string started at line %d is too long "
+ "(longer than %llu bytes)", lexer->token_line,
+ (long long) SIEVE_MAX_STRING_LEN);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ lexer->token_type = STT_STRING;
+ return TRUE;
+
+ /* single character tokens */
+ case ']':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_RSQUARE;
+ return TRUE;
+ case '[':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_LSQUARE;
+ return TRUE;
+ case '}':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_RCURLY;
+ return TRUE;
+ case '{':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_LCURLY;
+ return TRUE;
+ case ')':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_RBRACKET;
+ return TRUE;
+ case '(':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_LBRACKET;
+ return TRUE;
+ case ';':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_SEMICOLON;
+ return TRUE;
+ case ',':
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_COMMA;
+ return TRUE;
+
+ /* EOF */
+ case -1:
+ if (!scanner->input->eof) {
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+ lexer->token_type = STT_EOF;
+ return TRUE;
+
+ default:
+ /* number */
+ if (i_isdigit(sieve_lexer_curchar(scanner))) {
+ return sieve_lexer_scan_number(scanner);
+
+ /* identifier / tag */
+ } else if (i_isalpha(sieve_lexer_curchar(scanner)) ||
+ sieve_lexer_curchar(scanner) == '_' ||
+ sieve_lexer_curchar(scanner) == ':') {
+
+ enum sieve_token_type type = STT_IDENTIFIER;
+ str_truncate(lexer->token_str_value,0);
+ str = lexer->token_str_value;
+
+ /* If it starts with a ':' it is a tag and not an
+ identifier */
+ if (sieve_lexer_curchar(scanner) == ':') {
+ sieve_lexer_shift(scanner); // discard colon
+ type = STT_TAG;
+
+ /* First character still can't be a DIGIT */
+ if (i_isalpha(sieve_lexer_curchar(scanner)) ||
+ sieve_lexer_curchar(scanner) == '_') {
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ sieve_lexer_shift(scanner);
+ } else {
+ /* Hmm, otherwise it is just a spurious
+ colon */
+ lexer->token_type = STT_COLON;
+ return TRUE;
+ }
+ } else {
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ sieve_lexer_shift(scanner);
+ }
+
+ /* Scan the rest of the identifier */
+ while (i_isalnum(sieve_lexer_curchar(scanner)) ||
+ sieve_lexer_curchar(scanner) == '_') {
+
+ if (str_len(str) <= SIEVE_MAX_IDENTIFIER_LEN) {
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ }
+ sieve_lexer_shift(scanner);
+ }
+
+ /* Is this in fact a multiline text string ? */
+ if (sieve_lexer_curchar(scanner) == ':' &&
+ type == STT_IDENTIFIER && str_len(str) == 4 &&
+ strncasecmp(str_c(str), "text", 4) == 0) {
+ sieve_lexer_shift(scanner); // discard colon
+
+ /* Discard SP and HTAB whitespace */
+ while (sieve_lexer_curchar(scanner) == ' ' ||
+ sieve_lexer_curchar(scanner) == '\t')
+ sieve_lexer_shift(scanner);
+
+ /* Discard hash comment or handle single CRLF */
+ if (sieve_lexer_curchar(scanner) == '\r')
+ sieve_lexer_shift(scanner);
+ switch (sieve_lexer_curchar(scanner)) {
+ case '#':
+ if (!sieve_lexer_scan_hash_comment(scanner))
+ return FALSE;
+ if (scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "end of file before end of multi-line string");
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ } else if (scanner->input->stream_errno != 0) {
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+ break;
+ case '\n':
+ sieve_lexer_shift(scanner);
+ break;
+ case -1:
+ if (scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "end of file before end of multi-line string");
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ default:
+ sieve_lexer_error(lexer,
+ "invalid character %s after 'text:' in multiline string",
+ _char_sanitize(sieve_lexer_curchar(scanner)));
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ /* Start over */
+ str_truncate(str, 0);
+
+ /* Parse literal lines */
+ while (TRUE) {
+ bool cr_shifted = FALSE;
+
+ /* Remove dot-stuffing or detect end of text */
+ if (sieve_lexer_curchar(scanner) == '.') {
+ sieve_lexer_shift(scanner);
+
+ /* Check for CR.. */
+ if (sieve_lexer_curchar(scanner) == '\r') {
+ sieve_lexer_shift(scanner);
+ cr_shifted = TRUE;
+ }
+
+ /* ..LF */
+ if (sieve_lexer_curchar(scanner) == '\n') {
+ sieve_lexer_shift(scanner);
+
+ /* End of multi-line string */
+
+ /* Check whether length limit was violated */
+ if (str_len(str) > SIEVE_MAX_STRING_LEN) {
+ sieve_lexer_error(lexer,
+ "multi-line string started at line %d is too long "
+ "(longer than %llu bytes)", lexer->token_line,
+ (long long) SIEVE_MAX_STRING_LEN);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ lexer->token_type = STT_STRING;
+ return TRUE;
+ } else if (cr_shifted) {
+ /* Seen CR, but no LF */
+ if (sieve_lexer_curchar(scanner) != -1 ||
+ !scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "found stray carriage-return (CR) character "
+ "in multi-line string started at line %d",
+ lexer->token_line);
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ /* Handle dot-stuffing */
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append_c(str, '.');
+ if (sieve_lexer_curchar(scanner) == '.')
+ sieve_lexer_shift(scanner);
+ }
+
+ /* Scan the rest of the line */
+ while (sieve_lexer_curchar(scanner) != '\n' &&
+ sieve_lexer_curchar(scanner) != '\r') {
+
+ switch (sieve_lexer_curchar(scanner)) {
+ case -1:
+ if (scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "end of file before end of multi-line string");
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ case '\0':
+ sieve_lexer_error(lexer,
+ "encountered NUL character in quoted string "
+ "started at line %d", lexer->token_line);
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ default:
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append_c(str, sieve_lexer_curchar(scanner));
+ }
+
+ sieve_lexer_shift(scanner);
+ }
+
+ /* If exited loop due to CR, skip it */
+ if (sieve_lexer_curchar(scanner) == '\r')
+ sieve_lexer_shift(scanner);
+
+ /* Now we must see an LF */
+ if (sieve_lexer_curchar(scanner) != '\n') {
+ if (sieve_lexer_curchar(scanner) != -1 ||
+ !scanner->input->eof) {
+ sieve_lexer_error(lexer,
+ "found stray carriage-return (CR) character "
+ "in multi-line string started at line %d",
+ lexer->token_line);
+ }
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ if (str_len(str) <= SIEVE_MAX_STRING_LEN)
+ str_append(str, "\r\n");
+
+ sieve_lexer_shift(scanner);
+ }
+
+ i_unreached();
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ if (str_len(str) > SIEVE_MAX_IDENTIFIER_LEN) {
+ sieve_lexer_error(lexer,
+ "encountered impossibly long %s%s'",
+ (type == STT_TAG ? "tag identifier ':" :
+ "identifier '"),
+ str_sanitize(str_c(str),
+ SIEVE_MAX_IDENTIFIER_LEN));
+ lexer->token_type = STT_ERROR;
+ return FALSE;
+ }
+
+ lexer->token_type = type;
+ return TRUE;
+ }
+
+ /* Error (unknown character and EOF handled already) */
+ if (lexer->token_type != STT_GARBAGE) {
+ sieve_lexer_error(lexer,
+ "unexpected character(s) starting with %s",
+ _char_sanitize(sieve_lexer_curchar(scanner)));
+ }
+ sieve_lexer_shift(scanner);
+ lexer->token_type = STT_GARBAGE;
+ return FALSE;
+ }
+}
+
+void sieve_lexer_skip_token(const struct sieve_lexer *lexer)
+{
+ /* Scan token while skipping whitespace */
+ do {
+ struct sieve_lexical_scanner *scanner = lexer->scanner;
+
+ if (!sieve_lexer_scan_raw_token(scanner)) {
+ if (!scanner->input->eof &&
+ scanner->input->stream_errno != 0) {
+ sieve_critical(scanner->svinst, scanner->ehandler,
+ sieve_error_script_location(scanner->script,
+ scanner->current_line),
+ "error reading script",
+ "error reading script during lexical analysis: %s",
+ i_stream_get_error(scanner->input));
+ }
+ return;
+ }
+ } while (lexer->token_type == STT_WHITESPACE);
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-lexer.h b/pigeonhole/src/lib-sieve/sieve-lexer.h
new file mode 100644
index 0000000..95451cc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-lexer.h
@@ -0,0 +1,119 @@
+#ifndef SIEVE_LEXER_H
+#define SIEVE_LEXER_H
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve-common.h"
+
+enum sieve_token_type {
+ STT_NONE,
+ STT_WHITESPACE,
+ STT_EOF,
+
+ STT_NUMBER,
+ STT_IDENTIFIER,
+ STT_TAG,
+ STT_STRING,
+
+ STT_RBRACKET,
+ STT_LBRACKET,
+ STT_RCURLY,
+ STT_LCURLY,
+ STT_RSQUARE,
+ STT_LSQUARE,
+ STT_SEMICOLON,
+ STT_COMMA,
+
+ /* These are currently not used in the lexical specification, but a
+ token is assigned to these to generate proper error messages (these
+ are technically not garbage and possibly part of mistyped but
+ otherwise valid tokens).
+ */
+ STT_SLASH,
+ STT_COLON,
+
+ /* Error tokens */
+ STT_GARBAGE, /* Error reporting deferred to parser */
+ STT_ERROR /* Lexer is responsible for error, parser won't report
+ additional errors */
+};
+
+/*
+ * Lexer object
+ */
+
+struct sieve_lexical_scanner;
+
+struct sieve_lexer {
+ struct sieve_lexical_scanner *scanner;
+
+ enum sieve_token_type token_type;
+ string_t *token_str_value;
+ sieve_number_t token_int_value;
+
+ int token_line;
+};
+
+const struct sieve_lexer *
+sieve_lexer_create(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r);
+void sieve_lexer_free(const struct sieve_lexer **lexer);
+
+/*
+ * Scanning
+ */
+
+void sieve_lexer_skip_token(const struct sieve_lexer *lexer);
+
+/*
+ * Token access
+ */
+
+static inline enum sieve_token_type
+sieve_lexer_token_type(const struct sieve_lexer *lexer)
+{
+ return lexer->token_type;
+}
+
+static inline const string_t *
+sieve_lexer_token_str(const struct sieve_lexer *lexer)
+{
+ i_assert(lexer->token_type == STT_STRING);
+
+ return lexer->token_str_value;
+}
+
+static inline const char *
+sieve_lexer_token_ident(const struct sieve_lexer *lexer)
+{
+ i_assert(lexer->token_type == STT_TAG ||
+ lexer->token_type == STT_IDENTIFIER);
+
+ return str_c(lexer->token_str_value);
+}
+
+static inline sieve_number_t
+sieve_lexer_token_int(const struct sieve_lexer *lexer)
+{
+ i_assert(lexer->token_type == STT_NUMBER);
+
+ return lexer->token_int_value;
+}
+
+static inline bool sieve_lexer_eof(const struct sieve_lexer *lexer)
+{
+ return lexer->token_type == STT_EOF;
+}
+
+static inline int sieve_lexer_token_line(const struct sieve_lexer *lexer)
+{
+ return lexer->token_line;
+}
+
+const char *sieve_lexer_token_description(const struct sieve_lexer *lexer);
+
+void sieve_lexer_token_print(const struct sieve_lexer *lexer);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-limits.h b/pigeonhole/src/lib-sieve/sieve-limits.h
new file mode 100644
index 0000000..1adbaa6
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-limits.h
@@ -0,0 +1,45 @@
+#ifndef SIEVE_LIMITS_H
+#define SIEVE_LIMITS_H
+
+/*
+ * Scripts
+ */
+
+#define SIEVE_MAX_SCRIPT_NAME_LEN 256
+
+#define SIEVE_DEFAULT_MAX_SCRIPT_SIZE (1 << 20)
+
+#define SIEVE_MAX_LOOP_DEPTH 4
+
+/*
+ * Lexer
+ */
+
+#define SIEVE_MAX_STRING_LEN (1 << 20)
+#define SIEVE_MAX_IDENTIFIER_LEN 32
+
+/*
+ * AST
+ */
+
+#define SIEVE_MAX_COMMAND_ARGUMENTS 32
+#define SIEVE_MAX_BLOCK_NESTING 32
+#define SIEVE_MAX_TEST_NESTING 32
+
+/*
+ * Runtime
+ */
+
+#define SIEVE_MAX_MATCH_VALUES 32
+#define SIEVE_HIGH_CPU_TIME_MSECS 1500
+#define SIEVE_DEFAULT_MAX_CPU_TIME_SECS 30
+#define SIEVE_DEFAULT_RESOURCE_USAGE_TIMEOUT_SECS (60 * 60)
+
+/*
+ * Actions
+ */
+
+#define SIEVE_DEFAULT_MAX_ACTIONS 32
+#define SIEVE_DEFAULT_MAX_REDIRECTS 4
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-match-types.c b/pigeonhole/src/lib-sieve/sieve-match-types.c
new file mode 100644
index 0000000..a61737e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-match-types.c
@@ -0,0 +1,569 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-comparators.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-match-types.h"
+
+#include <string.h>
+
+/*
+ * Types
+ */
+
+struct sieve_match_values {
+ pool_t pool;
+ ARRAY(string_t *) values;
+ unsigned count;
+};
+
+/*
+ * Default match types
+ */
+
+const struct sieve_match_type_def *sieve_core_match_types[] = {
+ &is_match_type, &contains_match_type, &matches_match_type
+};
+
+const unsigned int sieve_core_match_types_count =
+ N_ELEMENTS(sieve_core_match_types);
+
+/*
+ * Match-type 'extension'
+ */
+
+static bool mtch_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr);
+
+const struct sieve_extension_def match_type_extension = {
+ .name = "@match-types",
+ .validator_load = mtch_validator_load
+};
+
+/*
+ * Validator context:
+ * name-based match-type registry.
+ */
+
+static struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *mcht_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ mcht_ext = sieve_get_match_type_extension(svinst);
+ return sieve_validator_object_registry_get(valdtr, mcht_ext);
+}
+
+void sieve_match_type_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_match_type_def *mcht_def)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ sieve_validator_object_registry_add(regs, ext, &mcht_def->obj_def);
+}
+
+static bool sieve_match_type_exists
+(struct sieve_validator *valdtr, const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ return sieve_validator_object_registry_find(regs, identifier, NULL);
+}
+
+static const struct sieve_match_type *sieve_match_type_create_instance
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+ struct sieve_object object;
+ struct sieve_match_type *mcht;
+
+ if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+ return NULL;
+
+ mcht = p_new(sieve_command_pool(cmd), struct sieve_match_type, 1);
+ mcht->object = object;
+ mcht->def = (const struct sieve_match_type_def *) object.def;
+
+ return mcht;
+}
+
+bool mtch_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ struct sieve_validator_object_registry *regs =
+ sieve_validator_object_registry_init(valdtr, ext);
+ unsigned int i;
+
+ /* Register core match-types */
+ for ( i = 0; i < sieve_core_match_types_count; i++ ) {
+ sieve_validator_object_registry_add
+ (regs, NULL, &(sieve_core_match_types[i]->obj_def));
+ }
+
+ return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+struct mtch_interpreter_context {
+ struct sieve_match_values *match_values;
+ bool match_values_enabled;
+};
+
+static void mtch_interpreter_free
+(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_interpreter *interp ATTR_UNUSED, void *context)
+{
+ struct mtch_interpreter_context *mctx =
+ (struct mtch_interpreter_context *) context;
+
+ if ( mctx->match_values != NULL ) {
+ pool_unref(&mctx->match_values->pool);
+ }
+}
+
+struct sieve_interpreter_extension
+mtch_interpreter_extension = {
+ .ext_def = &match_type_extension,
+ .free = mtch_interpreter_free
+};
+
+static inline struct mtch_interpreter_context *get_interpreter_context
+(struct sieve_interpreter *interp, bool create)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *mcht_ext;
+ struct mtch_interpreter_context *ctx;
+
+ svinst = sieve_interpreter_svinst(interp);
+ mcht_ext = sieve_get_match_type_extension(svinst);
+
+ ctx = (struct mtch_interpreter_context *)
+ sieve_interpreter_extension_get_context(interp, mcht_ext);
+
+ if ( ctx == NULL && create ) {
+ pool_t pool = sieve_interpreter_pool(interp);
+ ctx = p_new(pool, struct mtch_interpreter_context, 1);
+
+ sieve_interpreter_extension_register
+ (interp, mcht_ext, &mtch_interpreter_extension, (void *) ctx);
+ }
+
+ return ctx;
+}
+
+/*
+ * Match values
+ */
+
+bool sieve_match_values_set_enabled
+(const struct sieve_runtime_env *renv, bool enable)
+{
+ struct mtch_interpreter_context *ctx =
+ get_interpreter_context(renv->interp, enable);
+
+ if ( ctx != NULL ) {
+ bool previous = ctx->match_values_enabled;
+
+ ctx->match_values_enabled = enable;
+ return previous;
+ }
+
+ return FALSE;
+}
+
+bool sieve_match_values_are_enabled
+(const struct sieve_runtime_env *renv)
+{
+ struct mtch_interpreter_context *ctx =
+ get_interpreter_context(renv->interp, FALSE);
+
+ return ( ctx == NULL ? FALSE : ctx->match_values_enabled );
+}
+
+struct sieve_match_values *sieve_match_values_start
+(const struct sieve_runtime_env *renv)
+{
+ struct mtch_interpreter_context *ctx =
+ get_interpreter_context(renv->interp, FALSE);
+ struct sieve_match_values *match_values;
+
+ if ( ctx == NULL || !ctx->match_values_enabled )
+ return NULL;
+
+ pool_t pool = pool_alloconly_create("sieve_match_values", 1024);
+
+ match_values = p_new(pool, struct sieve_match_values, 1);
+ match_values->pool = pool;
+ match_values->count = 0;
+
+ p_array_init(&match_values->values, pool, 4);
+
+ return match_values;
+}
+
+static string_t *sieve_match_values_add_entry
+(struct sieve_match_values *mvalues)
+{
+ string_t *entry;
+
+ if ( mvalues == NULL ) return NULL;
+
+ if ( mvalues->count >= SIEVE_MAX_MATCH_VALUES ) return NULL;
+
+ if ( mvalues->count >= array_count(&mvalues->values) ) {
+ entry = str_new(mvalues->pool, 64);
+ array_append(&mvalues->values, &entry, 1); } else {
+ string_t * const *ep = array_idx(&mvalues->values, mvalues->count);
+ entry = *ep;
+ str_truncate(entry, 0);
+ }
+
+ mvalues->count++;
+
+ return entry;
+}
+
+void sieve_match_values_set
+(struct sieve_match_values *mvalues, unsigned int index, string_t *value)
+{
+ if ( mvalues != NULL && index < array_count(&mvalues->values) ) {
+ string_t * const *ep = array_idx(&mvalues->values, index);
+ string_t *entry = *ep;
+
+ if ( entry != NULL && value != NULL ) {
+ str_truncate(entry, 0);
+ str_append_str(entry, value);
+ }
+ }
+}
+
+void sieve_match_values_add
+(struct sieve_match_values *mvalues, string_t *value)
+{
+ string_t *entry = sieve_match_values_add_entry(mvalues);
+
+ if ( entry != NULL && value != NULL )
+ str_append_str(entry, value);
+}
+
+void sieve_match_values_add_char
+(struct sieve_match_values *mvalues, char c)
+{
+ string_t *entry = sieve_match_values_add_entry(mvalues);
+
+ if ( entry != NULL )
+ str_append_c(entry, c);
+}
+
+void sieve_match_values_skip
+(struct sieve_match_values *mvalues, int num)
+{
+ int i;
+
+ for ( i = 0; i < num; i++ )
+ (void) sieve_match_values_add_entry(mvalues);
+}
+
+void sieve_match_values_commit
+(const struct sieve_runtime_env *renv, struct sieve_match_values **mvalues)
+{
+ struct mtch_interpreter_context *ctx;
+
+ if ( (*mvalues) == NULL ) return;
+
+ ctx = get_interpreter_context(renv->interp, FALSE);
+ if ( ctx == NULL || !ctx->match_values_enabled )
+ return;
+
+ if ( ctx->match_values != NULL ) {
+ pool_unref(&ctx->match_values->pool);
+ ctx->match_values = NULL;
+ }
+
+ ctx->match_values = *mvalues;
+ *mvalues = NULL;
+}
+
+void sieve_match_values_abort
+(struct sieve_match_values **mvalues)
+{
+ if ( (*mvalues) == NULL ) return;
+
+ pool_unref(&(*mvalues)->pool);
+ *mvalues = NULL;
+}
+
+void sieve_match_values_get
+(const struct sieve_runtime_env *renv, unsigned int index, string_t **value_r)
+{
+ struct mtch_interpreter_context *ctx =
+ get_interpreter_context(renv->interp, FALSE);
+ struct sieve_match_values *mvalues;
+
+ if ( ctx == NULL || ctx->match_values == NULL ) {
+ *value_r = NULL;
+ return;
+ }
+
+ mvalues = ctx->match_values;
+ if ( index < array_count(&mvalues->values) && index < mvalues->count ) {
+ string_t * const *entry = array_idx(&mvalues->values, index);
+
+ *value_r = *entry;
+ return;
+ }
+
+ *value_r = NULL;
+}
+
+/*
+ * Match-type tagged argument
+ */
+
+/* Forward declarations */
+
+static bool tag_match_type_is_instance_of
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext, const char *identifier, void **data);
+static bool tag_match_type_validate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool tag_match_type_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/* Argument object */
+
+const struct sieve_argument_def match_type_tag = {
+ .identifier = "MATCH-TYPE",
+ .is_instance_of = tag_match_type_is_instance_of,
+ .validate = tag_match_type_validate,
+ .generate = tag_match_type_generate
+};
+
+/* Argument implementation */
+
+static bool tag_match_type_is_instance_of
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const struct sieve_extension *ext ATTR_UNUSED, const char *identifier,
+ void **data)
+{
+ const struct sieve_match_type *mcht;
+
+ if ( data == NULL )
+ return sieve_match_type_exists(valdtr, identifier);
+
+ if ( (mcht=sieve_match_type_create_instance
+ (valdtr, cmd, identifier)) == NULL )
+ return FALSE;
+
+ *data = (void *) mcht;
+ return TRUE;
+}
+
+static bool tag_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ const struct sieve_match_type *mcht =
+ (const struct sieve_match_type *) (*arg)->argument->data;
+ struct sieve_match_type_context *mtctx;
+
+ mtctx = p_new(sieve_command_pool(cmd), struct sieve_match_type_context, 1);
+ mtctx->match_type = mcht;
+ mtctx->argument = *arg;
+ mtctx->comparator = NULL; /* Can be filled in later */
+
+ (*arg)->argument->data = mtctx;
+
+ /* Syntax:
+ * ":is" / ":contains" / ":matches" (subject to extension)
+ */
+
+ /* Skip tag */
+ *arg = sieve_ast_argument_next(*arg);
+
+ /* Check whether this match type requires additional validation.
+ * Additional validation can override the match type recorded in the context
+ * for later code generation.
+ */
+ if ( mcht->def != NULL && mcht->def->validate != NULL ) {
+ return mcht->def->validate(valdtr, arg, mtctx);
+ }
+
+ return TRUE;
+}
+
+static bool tag_match_type_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ struct sieve_match_type_context *mtctx =
+ (struct sieve_match_type_context *) arg->argument->data;
+
+ (void) sieve_opr_match_type_emit(cgenv->sblock, mtctx->match_type);
+
+ return TRUE;
+}
+
+void sieve_match_types_link_tags
+(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg, int id_code)
+{
+ struct sieve_instance *svinst;
+ const struct sieve_extension *mcht_ext;
+
+ svinst = sieve_validator_svinst(valdtr);
+ mcht_ext = sieve_get_comparator_extension(svinst);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, mcht_ext, &match_type_tag, id_code);
+}
+
+/*
+ * Validation
+ */
+
+bool sieve_match_type_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *key_arg,
+ const struct sieve_match_type *mcht_default,
+ const struct sieve_comparator *cmp_default)
+{
+ struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);
+ struct sieve_ast_argument *mt_arg = NULL;
+ struct sieve_match_type_context *mtctx;
+ const struct sieve_match_type *mcht = NULL;
+ const struct sieve_comparator *cmp = NULL;
+
+ /* Find match type and comparator among the arguments */
+ while ( arg != NULL && arg != cmd->first_positional ) {
+ if ( sieve_argument_is_comparator(arg) ) {
+ cmp = sieve_comparator_tag_get(arg);
+ if ( mt_arg != NULL ) break;
+ }
+
+ if ( sieve_argument_is_match_type(arg) ) {
+ mt_arg = arg;
+ if ( cmp != NULL ) break;
+ }
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Verify using the default comparator if none is specified explicitly */
+ if ( cmp == NULL ) {
+ cmp = sieve_comparator_copy(sieve_command_pool(cmd), cmp_default);
+ }
+
+ /* Verify the default match type if none is specified explicitly */
+ if ( mt_arg == NULL || mt_arg->argument == NULL ||
+ mt_arg->argument->data == NULL ) {
+ mcht = sieve_match_type_copy(sieve_command_pool(cmd), mcht_default);
+ mtctx = t_new(struct sieve_match_type_context, 1);
+ mtctx->command = cmd;
+ mtctx->match_type = mcht;
+ } else {
+ mtctx = (struct sieve_match_type_context *) mt_arg->argument->data;
+ mcht = mtctx->match_type;
+ }
+ mtctx->comparator = cmp;
+
+ /* Check whether this match type requires additional validation.
+ * Additional validation can override the match type recorded in the context
+ * for later code generation.
+ */
+ if ( mcht != NULL && mcht->def != NULL &&
+ mcht->def->validate_context != NULL ) {
+ return mcht->def->validate_context(valdtr, mt_arg, mtctx, key_arg);
+ }
+
+ return TRUE;
+}
+
+void sieve_match_type_arguments_remove
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = sieve_command_first_argument(cmd);
+
+ /* Remove any comparator and match type arguments */
+ while ( arg != NULL && arg != cmd->first_positional ) {
+ if ( sieve_argument_is_comparator(arg) ) {
+ arg = sieve_ast_arguments_detach(arg, 1);
+ continue;
+ }
+
+ if ( sieve_argument_is_match_type(arg) ) {
+ arg = sieve_ast_arguments_detach(arg, 1);
+ continue;
+ }
+
+ arg = sieve_ast_argument_next(arg);
+ }
+}
+
+
+/*
+ * Match-type operand
+ */
+
+const struct sieve_operand_class sieve_match_type_operand_class =
+ { "match type" };
+
+static const struct sieve_extension_objects core_match_types =
+ SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_match_types);
+
+const struct sieve_operand_def match_type_operand = {
+ .name = "match-type",
+ .code = SIEVE_OPERAND_MATCH_TYPE,
+ .class = &sieve_match_type_operand_class,
+ .interface = &core_match_types
+};
+
+/*
+ * Common validation implementation
+ */
+
+bool sieve_match_substring_validate_context
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_match_type_context *ctx,
+ struct sieve_ast_argument *key_arg ATTR_UNUSED)
+{
+ const struct sieve_comparator *cmp = ctx->comparator;
+
+ if ( cmp == NULL || cmp->def == NULL )
+ return TRUE;
+
+ if ( (cmp->def->flags & SIEVE_COMPARATOR_FLAG_SUBSTRING_MATCH) == 0 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "the specified %s comparator does not support "
+ "sub-string matching as required by the :%s match type",
+ cmp->object.def->identifier, ctx->match_type->object.def->identifier );
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-match-types.h b/pigeonhole/src/lib-sieve/sieve-match-types.h
new file mode 100644
index 0000000..0417e2c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-match-types.h
@@ -0,0 +1,233 @@
+#ifndef SIEVE_MATCH_TYPES_H
+#define SIEVE_MATCH_TYPES_H
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-objects.h"
+
+/*
+ * Types
+ */
+
+struct sieve_match_type_context;
+
+/*
+ * Core match types
+ */
+
+enum sieve_match_type_code {
+ SIEVE_MATCH_TYPE_IS,
+ SIEVE_MATCH_TYPE_CONTAINS,
+ SIEVE_MATCH_TYPE_MATCHES,
+ SIEVE_MATCH_TYPE_CUSTOM
+};
+
+extern const struct sieve_match_type_def is_match_type;
+extern const struct sieve_match_type_def contains_match_type;
+extern const struct sieve_match_type_def matches_match_type;
+
+/*
+ * Match type definition
+ */
+
+struct sieve_match_type_def {
+ struct sieve_object_def obj_def;
+
+ bool (*validate)
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_match_type_context *ctx);
+ bool (*validate_context)
+ (struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_match_type_context *ctx, struct sieve_ast_argument *key_arg);
+
+ /*
+ * Matching
+ */
+
+ /* Custom implementation */
+
+ int (*match)
+ (struct sieve_match_context *mctx, struct sieve_stringlist *value_list,
+ struct sieve_stringlist *key_list);
+
+ /* Default match loop */
+
+ void (*match_init)(struct sieve_match_context *mctx);
+
+ int (*match_keys)
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ struct sieve_stringlist *key_list);
+ int (*match_key)
+ (struct sieve_match_context *mctx, const char *val, size_t val_size,
+ const char *key, size_t key_size);
+
+ void (*match_deinit)(struct sieve_match_context *mctx);
+};
+
+/*
+ * Match type instance
+ */
+
+struct sieve_match_type {
+ struct sieve_object object;
+
+ const struct sieve_match_type_def *def;
+};
+
+#define SIEVE_MATCH_TYPE_DEFAULT(definition) \
+ { SIEVE_OBJECT_DEFAULT(definition), &(definition) }
+
+#define sieve_match_type_name(mcht) \
+ ( (mcht)->object.def->identifier )
+#define sieve_match_type_is(mcht, definition) \
+ ( (mcht)->def == &(definition) )
+
+static inline const struct sieve_match_type *sieve_match_type_copy
+(pool_t pool, const struct sieve_match_type *cmp_orig)
+{
+ struct sieve_match_type *cmp = p_new(pool, struct sieve_match_type, 1);
+
+ *cmp = *cmp_orig;
+
+ return cmp;
+}
+
+/*
+ * Match type context
+ */
+
+struct sieve_match_type_context {
+ struct sieve_command *command;
+ struct sieve_ast_argument *argument;
+
+ const struct sieve_match_type *match_type;
+
+ /* Only filled in when match_type->validate_context() is called */
+ const struct sieve_comparator *comparator;
+
+ /* Context data could be used in the future to pass data between validator and
+ * generator in match types that use extra parameters. Currently not
+ * necessary, not even for the relational extension.
+ */
+ void *ctx_data;
+};
+
+/*
+ * Match type registration
+ */
+
+void sieve_match_type_register
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_match_type_def *mcht);
+
+/*
+ * Match values
+ */
+
+struct sieve_match_values;
+
+bool sieve_match_values_set_enabled
+ (const struct sieve_runtime_env *renv, bool enable);
+bool sieve_match_values_are_enabled
+ (const struct sieve_runtime_env *renv);
+
+struct sieve_match_values *sieve_match_values_start
+ (const struct sieve_runtime_env *renv);
+void sieve_match_values_set
+ (struct sieve_match_values *mvalues, unsigned int index, string_t *value);
+void sieve_match_values_add
+ (struct sieve_match_values *mvalues, string_t *value);
+void sieve_match_values_add_char
+ (struct sieve_match_values *mvalues, char c);
+void sieve_match_values_skip
+ (struct sieve_match_values *mvalues, int num);
+
+void sieve_match_values_commit
+ (const struct sieve_runtime_env *renv, struct sieve_match_values **mvalues);
+void sieve_match_values_abort
+ (struct sieve_match_values **mvalues);
+
+void sieve_match_values_get
+ (const struct sieve_runtime_env *renv, unsigned int index, string_t **value_r);
+
+/*
+ * Match type tagged argument
+ */
+
+extern const struct sieve_argument_def match_type_tag;
+
+static inline bool sieve_argument_is_match_type
+ (struct sieve_ast_argument *arg)
+{
+ return ( arg->argument->def == &match_type_tag );
+}
+
+void sieve_match_types_link_tags
+ (struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg, int id_code);
+
+/*
+ * Validation
+ */
+
+bool sieve_match_type_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *key_arg,
+ const struct sieve_match_type *mcht_default,
+ const struct sieve_comparator *cmp_default);
+
+void sieve_match_type_arguments_remove
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+
+/*
+ * Match type operand
+ */
+
+extern const struct sieve_operand_def match_type_operand;
+extern const struct sieve_operand_class sieve_match_type_operand_class;
+
+#define SIEVE_EXT_DEFINE_MATCH_TYPE(OP) SIEVE_EXT_DEFINE_OBJECT(OP)
+#define SIEVE_EXT_DEFINE_MATCH_TYPES(OPS) SIEVE_EXT_DEFINE_OBJECTS(OPS)
+
+static inline bool sieve_operand_is_match_type
+(const struct sieve_operand *operand)
+{
+ return ( operand != NULL && operand->def != NULL &&
+ operand->def->class == &sieve_match_type_operand_class );
+}
+
+static inline void sieve_opr_match_type_emit
+(struct sieve_binary_block *sblock, const struct sieve_match_type *mcht)
+{
+ sieve_opr_object_emit(sblock, mcht->object.ext, mcht->object.def);
+}
+
+static inline bool sieve_opr_match_type_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ return sieve_opr_object_dump
+ (denv, &sieve_match_type_operand_class, address, NULL);
+}
+
+static inline int sieve_opr_match_type_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_match_type *mcht)
+{
+ if ( !sieve_opr_object_read
+ (renv, &sieve_match_type_operand_class, address, &mcht->object) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ mcht->def = (const struct sieve_match_type_def *) mcht->object.def;
+ return SIEVE_EXEC_OK;
+}
+
+/* Common validation implementation */
+
+bool sieve_match_substring_validate_context
+ (struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_match_type_context *ctx,
+ struct sieve_ast_argument *key_arg);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-match.c b/pigeonhole/src/lib-sieve/sieve-match.c
new file mode 100644
index 0000000..37d37fe
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-match.c
@@ -0,0 +1,293 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "hash.h"
+#include "array.h"
+#include "str-sanitize.h"
+
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-runtime-trace.h"
+
+#include "sieve-match.h"
+
+/*
+ * Matching implementation
+ */
+
+struct sieve_match_context *sieve_match_begin
+(const struct sieve_runtime_env *renv,
+ const struct sieve_match_type *mcht,
+ const struct sieve_comparator *cmp)
+{
+ struct sieve_match_context *mctx;
+ pool_t pool;
+
+ /* Reject unimplemented match-type */
+ if ( mcht->def == NULL || (mcht->def->match == NULL &&
+ mcht->def->match_keys == NULL && mcht->def->match_key == NULL) )
+ return NULL;
+
+ /* Create match context */
+ pool = pool_alloconly_create("sieve_match_context", 1024);
+ mctx = p_new(pool, struct sieve_match_context, 1);
+ mctx->pool = pool;
+ mctx->runenv = renv;
+ mctx->match_type = mcht;
+ mctx->comparator = cmp;
+ mctx->exec_status = SIEVE_EXEC_OK;
+ mctx->trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
+
+ /* Trace */
+ if ( mctx->trace ) {
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0,
+ "starting `:%s' match with `%s' comparator:",
+ sieve_match_type_name(mcht), sieve_comparator_name(cmp));
+ }
+
+ /* Initialize match type */
+ if ( mcht->def != NULL && mcht->def->match_init != NULL ) {
+ mcht->def->match_init(mctx);
+ }
+
+ return mctx;
+}
+
+int sieve_match_value
+(struct sieve_match_context *mctx, const char *value, size_t value_size,
+ struct sieve_stringlist *key_list)
+{
+ const struct sieve_match_type *mcht = mctx->match_type;
+ const struct sieve_runtime_env *renv = mctx->runenv;
+ int match, ret;
+
+ if ( mctx->trace ) {
+ sieve_runtime_trace(renv, 0,
+ "matching value `%s'", str_sanitize(value, 80));
+ }
+
+ /* Match to key values */
+
+ sieve_stringlist_reset(key_list);
+
+ if ( mctx->trace )
+ sieve_stringlist_set_trace(key_list, TRUE);
+
+ sieve_runtime_trace_descend(renv);
+
+ if ( mcht->def->match_keys != NULL ) {
+ /* Call match-type's own key match handler */
+ match = mcht->def->match_keys(mctx, value, value_size, key_list);
+ } else {
+ string_t *key_item = NULL;
+
+ /* Default key match loop */
+ match = 0;
+ while ( match == 0 &&
+ (ret=sieve_stringlist_next_item(key_list, &key_item)) > 0 ) {
+ T_BEGIN {
+ match = mcht->def->match_key
+ (mctx, value, value_size, str_c(key_item), str_len(key_item));
+
+ if ( mctx->trace ) {
+ sieve_runtime_trace(renv, 0,
+ "with key `%s' => %d", str_sanitize(str_c(key_item), 80),
+ match);
+ }
+ } T_END;
+ }
+
+ if ( ret < 0 ) {
+ mctx->exec_status = key_list->exec_status;
+ match = -1;
+ }
+ }
+
+ sieve_runtime_trace_ascend(renv);
+
+ if ( mctx->match_status < 0 || match < 0 )
+ mctx->match_status = -1;
+ else
+ mctx->match_status =
+ ( mctx->match_status > match ? mctx->match_status : match );
+ return match;
+}
+
+int sieve_match_end(struct sieve_match_context **mctx, int *exec_status)
+{
+ const struct sieve_match_type *mcht = (*mctx)->match_type;
+ const struct sieve_runtime_env *renv = (*mctx)->runenv;
+ int match = (*mctx)->match_status;
+
+ if ( mcht->def != NULL && mcht->def->match_deinit != NULL )
+ mcht->def->match_deinit(*mctx);
+
+ if ( exec_status != NULL )
+ *exec_status = (*mctx)->exec_status;
+
+ pool_unref(&(*mctx)->pool);
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "finishing match with result: %s",
+ ( match > 0 ? "matched" : ( match < 0 ? "error" : "not matched" ) ));
+ sieve_runtime_trace_ascend(renv);
+
+ return match;
+}
+
+int sieve_match
+(const struct sieve_runtime_env *renv,
+ const struct sieve_match_type *mcht,
+ const struct sieve_comparator *cmp,
+ struct sieve_stringlist *value_list,
+ struct sieve_stringlist *key_list,
+ int *exec_status)
+{
+ struct sieve_match_context *mctx;
+ string_t *value_item = NULL;
+ int match, ret;
+
+ if ( (mctx=sieve_match_begin(renv, mcht, cmp)) == NULL )
+ return 0;
+
+ /* Match value to keys */
+
+ sieve_stringlist_reset(value_list);
+
+ if ( mctx->trace )
+ sieve_stringlist_set_trace(value_list, TRUE);
+
+ if ( mcht->def->match != NULL ) {
+ /* Call match-type's match handler */
+ match = mctx->match_status =
+ mcht->def->match(mctx, value_list, key_list);
+
+ } else {
+ /* Default value match loop */
+
+ match = 0;
+ while ( match == 0 &&
+ (ret=sieve_stringlist_next_item(value_list, &value_item)) > 0 ) {
+
+ match = sieve_match_value
+ (mctx, str_c(value_item), str_len(value_item), key_list);
+ }
+
+ if ( ret < 0 ) {
+ mctx->exec_status = value_list->exec_status;
+ match = -1;
+ }
+ }
+
+ (void)sieve_match_end(&mctx, exec_status);
+ return match;
+}
+
+/*
+ * Reading match operands
+ */
+
+int sieve_match_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address, int *opt_code)
+{
+ int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while ( opok ) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 )
+ return opt;
+
+ switch ( *opt_code ) {
+ case SIEVE_MATCH_OPT_COMPARATOR:
+ opok = sieve_opr_comparator_dump(denv, address);
+ break;
+ case SIEVE_MATCH_OPT_MATCH_TYPE:
+ opok = sieve_opr_match_type_dump(denv, address);
+ break;
+ default:
+ return ( final ? -1 : 1 );
+ }
+ }
+
+ return -1;
+}
+
+int sieve_match_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address, int *opt_code,
+ int *exec_status, struct sieve_comparator *cmp, struct sieve_match_type *mcht)
+{
+ int _opt_code = 0;
+ bool final = FALSE;
+ int status = SIEVE_EXEC_OK;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_OK;
+
+ while ( status == SIEVE_EXEC_OK ) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ){
+ if ( opt < 0 && exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return opt;
+ }
+
+ switch ( *opt_code ) {
+ case SIEVE_MATCH_OPT_COMPARATOR:
+ if (cmp == NULL) {
+ sieve_runtime_trace_error(renv, "unexpected comparator operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ status = sieve_opr_comparator_read(renv, address, cmp);
+ break;
+ case SIEVE_MATCH_OPT_MATCH_TYPE:
+ if (mcht == NULL) {
+ sieve_runtime_trace_error(renv, "unexpected match-type operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ status = sieve_opr_match_type_read(renv, address, mcht);
+ break;
+ default:
+ if ( final ) {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = status;
+ return -1;
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-match.h b/pigeonhole/src/lib-sieve/sieve-match.h
new file mode 100644
index 0000000..d4d726f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-match.h
@@ -0,0 +1,68 @@
+#ifndef SIEVE_MATCH_H
+#define SIEVE_MATCH_H
+
+#include "sieve-common.h"
+
+/*
+ * Matching context
+ */
+
+struct sieve_match_context {
+ pool_t pool;
+
+ const struct sieve_runtime_env *runenv;
+
+ const struct sieve_match_type *match_type;
+ const struct sieve_comparator *comparator;
+
+ void *data;
+
+ int match_status;
+ int exec_status;
+
+ bool trace:1;
+};
+
+/*
+ * Matching implementation
+ */
+
+/* Manual value iteration (for when multiple matches are allowed) */
+struct sieve_match_context *sieve_match_begin
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_match_type *mcht,
+ const struct sieve_comparator *cmp);
+int sieve_match_value
+ (struct sieve_match_context *mctx, const char *value, size_t value_size,
+ struct sieve_stringlist *key_list);
+int sieve_match_end(struct sieve_match_context **mctx, int *exec_status);
+
+/* Default matching operation */
+int sieve_match
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_match_type *mcht,
+ const struct sieve_comparator *cmp,
+ struct sieve_stringlist *value_list,
+ struct sieve_stringlist *key_list,
+ int *exec_status);
+
+/*
+ * Read matching operands
+ */
+
+enum sieve_match_opt_operand {
+ SIEVE_MATCH_OPT_END,
+ SIEVE_MATCH_OPT_COMPARATOR,
+ SIEVE_MATCH_OPT_MATCH_TYPE,
+ SIEVE_MATCH_OPT_LAST
+};
+
+int sieve_match_opr_optional_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address, int *opt_code);
+
+int sieve_match_opr_optional_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address, int *opt_code,
+ int *exec_status, struct sieve_comparator *cmp,
+ struct sieve_match_type *mcht);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-message.c b/pigeonhole/src/lib-sieve/sieve-message.c
new file mode 100644
index 0000000..d086883
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-message.c
@@ -0,0 +1,1845 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mempool.h"
+#include "array.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "time-util.h"
+#include "rfc822-parser.h"
+#include "message-date.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "message-header-decode.h"
+#include "mail-html2text.h"
+#include "mail-storage.h"
+#include "mail-user.h"
+#include "smtp-params.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "raw-storage.h"
+
+#include "edit-mail.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-error.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-address.h"
+#include "sieve-address-parts.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-match.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-message.h"
+
+/*
+ * Message transmission
+ */
+
+const char *sieve_message_get_new_id(const struct sieve_instance *svinst)
+{
+ static int count = 0;
+
+ return t_strdup_printf("<dovecot-sieve-%s-%s-%d@%s>",
+ dec2str(ioloop_timeval.tv_sec), dec2str(ioloop_timeval.tv_usec),
+ count++, svinst->hostname);
+}
+
+/*
+ * Message context
+ */
+
+struct sieve_message_header {
+ const char *name;
+
+ const unsigned char *value, *utf8_value;
+ size_t value_len, utf8_value_len;
+};
+
+struct sieve_message_part {
+ struct sieve_message_part *parent, *next, *children;
+
+ ARRAY(struct sieve_message_header) headers;
+
+ const char *content_type;
+ const char *content_disposition;
+
+ const char *decoded_body;
+ const char *text_body;
+ size_t decoded_body_size;
+ size_t text_body_size;
+
+ bool have_body:1; /* there's the empty end-of-headers line */
+ bool epilogue:1; /* this is a multipart epilogue */
+};
+
+struct sieve_message_version {
+ struct mail *mail;
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct edit_mail *edit_mail;
+};
+
+struct sieve_message_context {
+ pool_t pool;
+ pool_t context_pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+ struct timeval time;
+
+ struct mail_user *mail_user;
+ const struct sieve_message_data *msgdata;
+
+ /* Message versioning */
+
+ struct mail_user *raw_mail_user;
+ ARRAY(struct sieve_message_version) versions;
+
+ /* Context data for extensions */
+
+ ARRAY(void *) ext_contexts;
+
+ /* Body */
+
+ ARRAY(struct sieve_message_part *) cached_body_parts;
+ ARRAY(struct sieve_message_part_data) return_body_parts;
+ buffer_t *raw_body;
+
+ bool edit_snapshot:1;
+ bool substitute_snapshot:1;
+};
+
+/*
+ * Message versions
+ */
+
+static inline struct sieve_message_version *sieve_message_version_new
+(struct sieve_message_context *msgctx)
+{
+ return array_append_space(&msgctx->versions);
+}
+
+static inline struct sieve_message_version *sieve_message_version_get
+(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *versions;
+ unsigned int count;
+
+ versions = array_get_modifiable(&msgctx->versions, &count);
+ if ( count == 0 )
+ return array_append_space(&msgctx->versions);
+
+ return &versions[count-1];
+}
+
+static inline void sieve_message_version_free
+(struct sieve_message_version *version)
+{
+ if ( version->edit_mail != NULL ) {
+ edit_mail_unwrap(&version->edit_mail);
+ version->edit_mail = NULL;
+ }
+
+ if ( version->mail != NULL ) {
+ mail_free(&version->mail);
+ mailbox_transaction_rollback(&version->trans);
+ mailbox_free(&version->box);
+ version->mail = NULL;
+ }
+}
+
+/*
+ * Message context object
+ */
+
+struct sieve_message_context *sieve_message_context_create
+(struct sieve_instance *svinst, struct mail_user *mail_user,
+ const struct sieve_message_data *msgdata)
+{
+ struct sieve_message_context *msgctx;
+
+ msgctx = i_new(struct sieve_message_context, 1);
+ msgctx->refcount = 1;
+ msgctx->svinst = svinst;
+
+ msgctx->mail_user = mail_user;
+ msgctx->msgdata = msgdata;
+
+ i_gettimeofday(&msgctx->time);
+
+ sieve_message_context_reset(msgctx);
+
+ return msgctx;
+}
+
+void sieve_message_context_ref(struct sieve_message_context *msgctx)
+{
+ msgctx->refcount++;
+}
+
+static void sieve_message_context_clear(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *versions;
+ unsigned int count, i;
+
+ if ( msgctx->pool != NULL ) {
+ versions = array_get_modifiable(&msgctx->versions, &count);
+
+ for ( i = 0; i < count; i++ ) {
+ sieve_message_version_free(&versions[i]);
+ }
+
+ pool_unref(&(msgctx->pool));
+ }
+}
+
+void sieve_message_context_unref(struct sieve_message_context **msgctx)
+{
+ i_assert((*msgctx)->refcount > 0);
+
+ if (--(*msgctx)->refcount != 0)
+ return;
+
+ if ( (*msgctx)->raw_mail_user != NULL )
+ mail_user_unref(&(*msgctx)->raw_mail_user);
+
+ sieve_message_context_clear(*msgctx);
+
+ if ( (*msgctx)->context_pool != NULL )
+ pool_unref(&((*msgctx)->context_pool));
+
+ i_free(*msgctx);
+ *msgctx = NULL;
+}
+
+static void sieve_message_context_flush(struct sieve_message_context *msgctx)
+{
+ pool_t pool;
+
+ if ( msgctx->context_pool != NULL )
+ pool_unref(&(msgctx->context_pool));
+
+ msgctx->context_pool = pool =
+ pool_alloconly_create("sieve_message_context_data", 2048);
+
+ p_array_init(&msgctx->ext_contexts, pool,
+ sieve_extensions_get_count(msgctx->svinst));
+
+ p_array_init(&msgctx->cached_body_parts, pool, 8);
+ p_array_init(&msgctx->return_body_parts, pool, 8);
+ msgctx->raw_body = NULL;
+}
+
+void sieve_message_context_reset(struct sieve_message_context *msgctx)
+{
+ sieve_message_context_clear(msgctx);
+
+ msgctx->pool = pool_alloconly_create("sieve_message_context", 1024);
+
+ p_array_init(&msgctx->versions, msgctx->pool, 4);
+
+ sieve_message_context_flush(msgctx);
+}
+
+pool_t sieve_message_context_pool(struct sieve_message_context *msgctx)
+{
+ return msgctx->context_pool;
+}
+
+void sieve_message_context_time(struct sieve_message_context *msgctx,
+ struct timeval *time)
+{
+ *time = msgctx->time;
+}
+
+/* Extension support */
+
+void sieve_message_context_extension_set
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext,
+ void *context)
+{
+ if ( ext->id < 0 ) return;
+
+ array_idx_set(&msgctx->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *sieve_message_context_extension_get
+(struct sieve_message_context *msgctx, const struct sieve_extension *ext)
+{
+ void * const *ctx;
+
+ if ( ext->id < 0 || ext->id >= (int) array_count(&msgctx->ext_contexts) )
+ return NULL;
+
+ ctx = array_idx(&msgctx->ext_contexts, (unsigned int) ext->id);
+
+ return *ctx;
+}
+
+/* Envelope */
+
+const struct smtp_address *sieve_message_get_orig_recipient
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *orcpt_to = NULL;
+
+ if ( msgdata->envelope.rcpt_params != NULL ) {
+ orcpt_to = msgdata->envelope.rcpt_params->orcpt.addr;
+ if ( !smtp_address_isnull(orcpt_to) )
+ return orcpt_to;
+ }
+
+ orcpt_to = msgdata->envelope.rcpt_to;
+ return ( !smtp_address_isnull(orcpt_to) ? orcpt_to : NULL );
+}
+
+const struct smtp_address *sieve_message_get_final_recipient
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *rcpt_to = msgdata->envelope.rcpt_to;
+
+ return ( !smtp_address_isnull(rcpt_to) ? rcpt_to : NULL);
+}
+
+const struct smtp_address *sieve_message_get_sender
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_data *msgdata = msgctx->msgdata;
+ const struct smtp_address *mail_from = msgdata->envelope.mail_from;
+
+ return ( !smtp_address_isnull(mail_from) ? mail_from : NULL);
+}
+
+/*
+ * Mail
+ */
+
+int sieve_message_substitute
+(struct sieve_message_context *msgctx, struct istream *input)
+{
+ static const char *wanted_headers[] = {
+ "From", "Message-ID", "Subject", "Return-Path", NULL
+ };
+ static const struct smtp_address default_sender = {
+ .localpart = DEFAULT_ENVELOPE_SENDER,
+ .domain = NULL,
+ };
+ struct mail_user *mail_user = msgctx->mail_user;
+ struct sieve_message_version *version;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ struct mailbox *box = NULL;
+ const struct smtp_address *sender;
+ int ret;
+
+ i_assert(input->blocking);
+
+ if ( msgctx->raw_mail_user == NULL ) {
+ void **sets = master_service_settings_get_others(master_service);
+
+ msgctx->raw_mail_user =
+ raw_storage_create_from_set(mail_user->set_info, sets[0]);
+ }
+
+ i_stream_seek(input, 0);
+ sender = sieve_message_get_sender(msgctx);
+ sender = (sender == NULL ? &default_sender : sender);
+ ret = raw_mailbox_alloc_stream(msgctx->raw_mail_user, input, (time_t)-1,
+ smtp_address_encode(sender), &box);
+
+ if ( ret < 0 ) {
+ e_error(msgctx->svinst->event,
+ "can't open substituted mail as raw: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ return -1;
+ }
+
+ if ( msgctx->substitute_snapshot ) {
+ version = sieve_message_version_new(msgctx);
+ } else {
+ version = sieve_message_version_get(msgctx);
+ sieve_message_version_free(version);
+ }
+
+ version->box = box;
+ version->trans = mailbox_transaction_begin(box, 0, __func__);
+ headers_ctx = mailbox_header_lookup_init(box, wanted_headers);
+ version->mail = mail_alloc(version->trans, 0, headers_ctx);
+ mailbox_header_lookup_unref(&headers_ctx);
+ mail_set_seq(version->mail, 1);
+
+ sieve_message_context_flush(msgctx);
+
+ msgctx->substitute_snapshot = FALSE;
+ msgctx->edit_snapshot = FALSE;
+
+ return 1;
+}
+
+struct mail *sieve_message_get_mail
+(struct sieve_message_context *msgctx)
+{
+ const struct sieve_message_version *versions;
+ unsigned int count;
+
+ versions = array_get(&msgctx->versions, &count);
+ if ( count == 0 )
+ return msgctx->msgdata->mail;
+
+ if ( versions[count-1].edit_mail != NULL )
+ return edit_mail_get_mail(versions[count-1].edit_mail);
+
+ return versions[count-1].mail;
+}
+
+struct edit_mail *sieve_message_edit
+(struct sieve_message_context *msgctx)
+{
+ struct sieve_message_version *version;
+
+ version = sieve_message_version_get(msgctx);
+
+ if ( version->edit_mail == NULL ) {
+ version->edit_mail = edit_mail_wrap
+ (( version->mail == NULL ? msgctx->msgdata->mail : version->mail ));
+ } else if ( msgctx->edit_snapshot ) {
+ version->edit_mail = edit_mail_snapshot(version->edit_mail);
+ }
+
+ msgctx->edit_snapshot = FALSE;
+
+ return version->edit_mail;
+}
+
+void sieve_message_snapshot
+(struct sieve_message_context *msgctx)
+{
+ msgctx->edit_snapshot = TRUE;
+ msgctx->substitute_snapshot = TRUE;
+}
+
+/*
+ * Message header list
+ */
+
+/* Forward declarations */
+
+static int sieve_message_header_list_next_item
+ (struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r);
+static int sieve_message_header_list_next_value
+ (struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_message_header_list_reset
+ (struct sieve_stringlist *_strlist);
+
+/* String list object */
+
+struct sieve_message_header_list {
+ struct sieve_header_list hdrlist;
+
+ struct sieve_stringlist *field_names;
+
+ const char *header_name;
+ const char *const *headers;
+ int headers_index;
+
+ bool mime_decode:1;
+};
+
+struct sieve_header_list *sieve_message_header_list_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ bool mime_decode)
+{
+ struct sieve_message_header_list *hdrlist;
+
+ hdrlist = t_new(struct sieve_message_header_list, 1);
+ hdrlist->hdrlist.strlist.runenv = renv;
+ hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ hdrlist->hdrlist.strlist.next_item = sieve_message_header_list_next_value;
+ hdrlist->hdrlist.strlist.reset = sieve_message_header_list_reset;
+ hdrlist->hdrlist.next_item = sieve_message_header_list_next_item;
+ hdrlist->field_names = field_names;
+ hdrlist->mime_decode = mime_decode;
+
+ return &hdrlist->hdrlist;
+}
+
+// NOTE: get rid of this once we have a proper Sieve string type
+static inline string_t *_header_right_trim(const char *raw)
+{
+ string_t *result;
+ const char *p, *pend;
+
+ pend = raw + strlen(raw);
+ if (raw == pend) {
+ result = t_str_new(1);
+ } else {
+ for ( p = pend-1; p >= raw; p-- ) {
+ if ( *p != ' ' && *p != '\t' ) break;
+ }
+ result = t_str_new(p - raw + 1);
+ str_append_data(result, raw, p - raw + 1);
+ }
+ return result;
+}
+
+/* String list implementation */
+
+static int sieve_message_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r)
+{
+ struct sieve_message_header_list *hdrlist =
+ (struct sieve_message_header_list *) _hdrlist;
+ const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+
+ if ( name_r != NULL )
+ *name_r = NULL;
+ *value_r = NULL;
+
+ /* Check for end of current header list */
+ if ( hdrlist->headers == NULL ) {
+ hdrlist->headers_index = 0;
+ } else if ( hdrlist->headers[hdrlist->headers_index] == NULL ) {
+ hdrlist->headers = NULL;
+ hdrlist->headers_index = 0;
+ }
+
+ /* Fetch next header */
+ while ( hdrlist->headers == NULL ) {
+ string_t *hdr_item = NULL;
+ int ret;
+
+ /* Read next header name from source list */
+ if ( (ret=sieve_stringlist_next_item
+ (hdrlist->field_names, &hdr_item)) <= 0 )
+ return ret;
+
+ hdrlist->header_name = str_c(hdr_item);
+
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracting `%s' headers from message",
+ str_sanitize(str_c(hdr_item), 80));
+ }
+
+ /* Fetch all matching headers from the e-mail */
+ if ( hdrlist->mime_decode ) {
+ ret = mail_get_headers_utf8(mail,
+ str_c(hdr_item), &hdrlist->headers);
+ } else {
+ ret = mail_get_headers(mail,
+ str_c(hdr_item), &hdrlist->headers);
+ }
+
+ if (ret < 0) {
+ _hdrlist->strlist.exec_status =
+ sieve_runtime_mail_error(renv, mail,
+ "failed to read header field `%s'", str_c(hdr_item));
+ return -1;
+ }
+
+ if ( ret == 0 || hdrlist->headers[0] == NULL ) {
+ /* Try next item when no headers found */
+ hdrlist->headers = NULL;
+ }
+ }
+
+ /* Return next item */
+ if ( name_r != NULL )
+ *name_r = hdrlist->header_name;
+ *value_r = _header_right_trim(hdrlist->headers[hdrlist->headers_index++]);
+ return 1;
+}
+
+static int sieve_message_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+ struct sieve_header_list *hdrlist =
+ (struct sieve_header_list *) _strlist;
+
+ return sieve_message_header_list_next_item
+ (hdrlist, NULL, value_r);
+}
+
+static void sieve_message_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+ struct sieve_message_header_list *hdrlist =
+ (struct sieve_message_header_list *) strlist;
+
+ hdrlist->headers = NULL;
+ hdrlist->headers_index = 0;
+ sieve_stringlist_reset(hdrlist->field_names);
+}
+
+/*
+ * Header override operand
+ */
+
+const struct sieve_operand_class sieve_message_override_operand_class =
+ { "header-override" };
+
+bool sieve_opr_message_override_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ struct sieve_message_override svmo;
+ const struct sieve_message_override_def *hodef;
+
+ if ( !sieve_opr_object_dump
+ (denv, &sieve_message_override_operand_class, address, &svmo.object) )
+ return FALSE;
+
+ hodef = svmo.def =
+ (const struct sieve_message_override_def *) svmo.object.def;
+
+ if ( hodef->dump_context != NULL ) {
+ sieve_code_descend(denv);
+ if ( !hodef->dump_context(&svmo, denv, address) ) {
+ return FALSE;
+ }
+ sieve_code_ascend(denv);
+ }
+
+ return TRUE;
+}
+
+int sieve_opr_message_override_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_message_override *svmo)
+{
+ const struct sieve_message_override_def *hodef;
+ int ret;
+
+ svmo->context = NULL;
+
+ if ( !sieve_opr_object_read
+ (renv, &sieve_message_override_operand_class, address, &svmo->object) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ hodef = svmo->def =
+ (const struct sieve_message_override_def *) svmo->object.def;
+
+ if ( hodef->read_context != NULL &&
+ (ret=hodef->read_context(svmo, renv, address, &svmo->context)) <= 0 )
+ return ret;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Optional operands
+ */
+
+int sieve_message_opr_optional_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE, opok = TRUE;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ while ( opok ) {
+ int opt;
+
+ if ( (opt=sieve_addrmatch_opr_optional_dump
+ (denv, address, opt_code)) <= 0 )
+ return opt;
+
+ if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+ opok = sieve_opr_message_override_dump(denv, address);
+ } else {
+ return ( final ? -1 : 1 );
+ }
+ }
+
+ return -1;
+}
+
+int sieve_message_opr_optional_read
+(const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code, int *exec_status,
+ struct sieve_address_part *addrp, struct sieve_match_type *mcht,
+ struct sieve_comparator *cmp,
+ ARRAY_TYPE(sieve_message_override) *svmos)
+{
+ signed int _opt_code = 0;
+ bool final = FALSE;
+ int ret;
+
+ if ( opt_code == NULL ) {
+ opt_code = &_opt_code;
+ final = TRUE;
+ }
+
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_OK;
+
+ for ( ;; ) {
+ int opt;
+
+ if ( (opt=sieve_addrmatch_opr_optional_read
+ (renv, address, opt_code, exec_status, addrp, mcht, cmp)) <= 0 )
+ return opt;
+
+ if ( *opt_code == SIEVE_OPT_MESSAGE_OVERRIDE ) {
+ struct sieve_message_override svmo;
+ const struct sieve_message_override *svmo_idx;
+ unsigned int count, i;
+
+ if ( (ret=sieve_opr_message_override_read
+ (renv, address, &svmo)) <= 0 ) {
+ if ( exec_status != NULL )
+ *exec_status = ret;
+ return -1;
+ }
+
+ if ( !array_is_created(svmos) )
+ t_array_init(svmos, 8);
+ /* insert in sorted sequence */
+ svmo_idx = array_get(svmos, &count);
+ for (i = 0; i < count; i++) {
+ if (svmo.def->sequence < svmo_idx[i].def->sequence) {
+ array_insert(svmos, i, &svmo, 1);
+ break;
+ }
+ }
+ if (count == i)
+ array_append(svmos, &svmo, 1);
+ } else {
+ if ( final ) {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ if ( exec_status != NULL )
+ *exec_status = SIEVE_EXEC_BIN_CORRUPT;
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+/*
+ * Message header
+ */
+
+int sieve_message_get_header_fields
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ ARRAY_TYPE(sieve_message_override) *svmos,
+ bool mime_decode, struct sieve_stringlist **fields_r)
+{
+ const struct sieve_message_override *svmo;
+ unsigned int count, i;
+ int ret;
+
+ if ( svmos == NULL || !array_is_created(svmos) ||
+ array_count(svmos) == 0 ) {
+ struct sieve_header_list *headers;
+ headers = sieve_message_header_list_create
+ (renv, field_names, mime_decode);
+ *fields_r = &headers->strlist;
+ return SIEVE_EXEC_OK;
+ }
+
+ svmo = array_get(svmos, &count);
+ if ( svmo[0].def->sequence == 0 &&
+ svmo[0].def->header_override != NULL ) {
+ *fields_r = field_names;
+ } else {
+ struct sieve_header_list *headers;
+ headers = sieve_message_header_list_create
+ (renv, field_names, mime_decode);
+ *fields_r = &headers->strlist;
+ }
+
+ for ( i = 0; i < count; i++ ) {
+ if ( svmo[i].def->header_override != NULL &&
+ (ret=svmo[i].def->header_override
+ (&svmo[i], renv, mime_decode, fields_r)) <= 0 )
+ return ret;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part
+ */
+
+struct sieve_message_part *sieve_message_part_parent
+(struct sieve_message_part *mpart)
+{
+ return mpart->parent;
+}
+
+struct sieve_message_part *sieve_message_part_next
+(struct sieve_message_part *mpart)
+{
+ return mpart->next;
+}
+
+struct sieve_message_part *sieve_message_part_children
+(struct sieve_message_part *mpart)
+{
+ return mpart->children;
+}
+
+const char *sieve_message_part_content_type
+(struct sieve_message_part *mpart)
+{
+ return mpart->content_type;
+}
+
+const char *sieve_message_part_content_disposition
+(struct sieve_message_part *mpart)
+{
+ return mpart->content_disposition;
+}
+
+int sieve_message_part_get_first_header
+(struct sieve_message_part *mpart, const char *field,
+ const char **value_r)
+{
+ const struct sieve_message_header *headers;
+ unsigned int i, count;
+
+ headers = array_get(&mpart->headers, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( strcasecmp( headers[i].name, field) == 0 ) {
+ i_assert( headers[i].value[headers[i].value_len] == '\0' );
+ *value_r = (const char *)headers[i].value;
+ return 1;
+ }
+ }
+
+ *value_r = NULL;
+ return 0;
+}
+
+void sieve_message_part_get_data
+(struct sieve_message_part *mpart,
+ struct sieve_message_part_data *data, bool text)
+{
+ i_zero(data);
+ data->content_type = mpart->content_type;
+ data->content_disposition = mpart->content_disposition;
+
+ if ( !text ) {
+ data->content = mpart->decoded_body;
+ data->size = mpart->decoded_body_size;
+ } else if ( mpart->children != NULL ) {
+ data->content = "";
+ data->size = 0;
+ } else {
+ data->content = mpart->text_body;
+ data->size = mpart->text_body_size;
+ }
+}
+
+/*
+ * Message body
+ */
+
+static void str_replace_nuls(string_t *str)
+{
+ char *data = str_c_modifiable(str);
+ unsigned int i, len = str_len(str);
+
+ for (i = 0; i < len; i++) {
+ if (data[i] == '\0')
+ data[i] = ' ';
+ }
+}
+
+static bool _is_wanted_content_type
+(const char * const *wanted_types, const char *content_type)
+ATTR_NULL(1)
+{
+ const char *subtype;
+ size_t type_len;
+
+ if ( wanted_types == NULL )
+ return TRUE;
+
+ subtype = strchr(content_type, '/');
+ type_len = ( subtype == NULL ? strlen(content_type) :
+ (size_t)(subtype - content_type) );
+
+ i_assert( wanted_types != NULL );
+
+ for (; *wanted_types != NULL; wanted_types++) {
+ const char *wanted_subtype;
+
+ if (**wanted_types == '\0') {
+ /* empty string matches everything */
+ return TRUE;
+ }
+
+ wanted_subtype = strchr(*wanted_types, '/');
+ if (wanted_subtype == NULL) {
+ /* match only main type */
+ if (strlen(*wanted_types) == type_len &&
+ strncasecmp(*wanted_types, content_type, type_len) == 0)
+ return TRUE;
+ } else {
+ /* match whole type/subtype */
+ if (strcasecmp(*wanted_types, content_type) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool sieve_message_body_get_return_parts
+(const struct sieve_runtime_env *renv,
+ const char * const *wanted_types,
+ bool extract_text)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *body_parts;
+ unsigned int i, count;
+ struct sieve_message_part_data *return_part;
+
+ /* Check whether any body parts are cached already */
+ body_parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( count == 0 )
+ return FALSE;
+
+ /* Clear result array */
+ array_clear(&msgctx->return_body_parts);
+
+ /* Fill result array with requested content_types */
+ for (i = 0; i < count; i++) {
+ if (!body_parts[i]->have_body) {
+ /* Part has no body; according to RFC this MUST not match to anything and
+ * therefore it is not included in the result.
+ */
+ continue;
+ }
+
+ /* Skip content types that are not requested */
+ if (!_is_wanted_content_type
+ (wanted_types, body_parts[i]->content_type))
+ continue;
+
+ /* Add new item to the result */
+ return_part = array_append_space(&msgctx->return_body_parts);
+ return_part->content_type = body_parts[i]->content_type;
+ return_part->content_disposition = body_parts[i]->content_disposition;
+
+ /* Depending on whether a decoded body part is requested, the appropriate
+ * cache item is read. If it is missing, this function fails and the cache
+ * needs to be completed by sieve_message_parts_add_missing().
+ */
+ if (extract_text) {
+ if (body_parts[i]->text_body == NULL)
+ return FALSE;
+ return_part->content = body_parts[i]->text_body;
+ return_part->size = body_parts[i]->text_body_size;
+ } else {
+ if (body_parts[i]->decoded_body == NULL)
+ return FALSE;
+ return_part->content = body_parts[i]->decoded_body;
+ return_part->size = body_parts[i]->decoded_body_size;
+ }
+ }
+
+ return TRUE;
+}
+
+static void sieve_message_part_save
+(const struct sieve_runtime_env *renv, buffer_t *buf,
+ struct sieve_message_part *body_part,
+ bool extract_text)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ pool_t pool = msgctx->context_pool;
+ buffer_t *result_buf, *text_buf = NULL;
+ char *part_data;
+ size_t part_size;
+
+ /* Extract text if requested */
+ result_buf = buf;
+ if ( extract_text && body_part->children == NULL &&
+ !body_part->epilogue ) {
+
+ if ( buf->used > 0 && mail_html2text_content_type_match
+ (body_part->content_type) ) {
+ struct mail_html2text *html2text;
+
+ text_buf = buffer_create_dynamic(default_pool, 4096);
+
+ /* Remove HTML markup */
+ html2text = mail_html2text_init(0);
+ mail_html2text_more(html2text, buf->data, buf->used, text_buf);
+ mail_html2text_deinit(&html2text);
+
+ result_buf = text_buf;
+ }
+ }
+
+ /* Add terminating NUL to the body part buffer */
+ buffer_append_c(result_buf, '\0');
+
+ /* Make copy of the buffer */
+ part_data = p_malloc(pool, result_buf->used);
+ memcpy(part_data, result_buf->data, result_buf->used);
+ part_size = result_buf->used - 1;
+
+ /* Free text buffer if used */
+ if ( text_buf != NULL)
+ buffer_free(&text_buf);
+
+ /* Depending on whether the part is processed into text, store message
+ * body in the appropriate cache location.
+ */
+ if ( !extract_text ) {
+ body_part->decoded_body = part_data;
+ body_part->decoded_body_size = part_size;
+ } else {
+ body_part->text_body = part_data;
+ body_part->text_body_size = part_size;
+ }
+
+ /* Clear buffer */
+ buffer_set_used_size(buf, 0);
+}
+
+static const char *
+_parse_content_type(const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_type;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type */
+ content_type = t_str_new(64);
+ if (rfc822_parse_content_type(&parser, content_type) < 0)
+ return "";
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end && *parser.data != ';' )
+ return "";
+
+ /* Success */
+ return str_c(content_type);
+}
+
+static const char *
+_parse_content_disposition(const struct message_header_line *hdr)
+{
+ struct rfc822_parser_context parser;
+ string_t *content_disp;
+
+ /* Initialize parsing */
+ rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
+ (void)rfc822_skip_lwsp(&parser);
+
+ /* Parse content type */
+ content_disp = t_str_new(64);
+ if (rfc822_parse_mime_token(&parser, content_disp) < 0)
+ return "";
+
+ /* Content-type value must end here, otherwise it is invalid after all */
+ (void)rfc822_skip_lwsp(&parser);
+ if ( parser.data != parser.end && *parser.data != ';' )
+ return "";
+
+ /* Success */
+ return str_c(content_disp);
+}
+
+/* sieve_message_parts_add_missing():
+ * Add requested message body parts to the cache that are missing.
+ */
+static int sieve_message_parts_add_missing
+(const struct sieve_runtime_env *renv,
+ const char *const *content_types,
+ bool extract_text, bool iter_all)
+ ATTR_NULL(2)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ pool_t pool = msgctx->context_pool;
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ struct message_parser_settings mparser_set = {
+ .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP,
+ .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS,
+ };
+ ARRAY(struct sieve_message_header) headers;
+ struct sieve_message_part *body_part, *header_part, *last_part;
+ struct message_parser_ctx *parser;
+ struct message_decoder_context *decoder;
+ struct message_block block, decoded;
+ struct message_part *mparts, *prev_mpart = NULL;
+ buffer_t *buf;
+ struct istream *input;
+ unsigned int idx = 0;
+ bool save_body = FALSE, have_all;
+ string_t *hdr_content = NULL;
+
+ /* First check whether any are missing */
+ if ( !iter_all && sieve_message_body_get_return_parts
+ (renv, content_types, extract_text) ) {
+ /* Cache hit; all are present */
+ return SIEVE_EXEC_OK;
+ }
+
+ /* Get the message stream */
+ if ( mail_get_stream(mail, NULL, NULL, &input) < 0 ) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to open input message");
+ }
+ if (mail_get_parts(mail, &mparts) < 0) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to parse input message parts");
+ }
+
+ buf = buffer_create_dynamic(default_pool, 4096);
+ body_part = header_part = last_part = NULL;
+
+ if (iter_all) {
+ t_array_init(&headers, 64);
+ hdr_content = t_str_new(512);
+ mparser_set.hdr_flags |= MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+ } else {
+ i_zero(&headers);
+ }
+
+ /* Initialize body decoder */
+ decoder = message_decoder_init(NULL, 0);
+
+ // FIXME: currently not tested with edit-mail.
+ //parser = message_parser_init_from_parts(parts, input,
+ // hparser_flags, mparser_flags);
+ parser = message_parser_init(pool_datastack_create(),
+ input, &mparser_set);
+ while ( message_parser_parse_next_block(parser, &block) > 0 ) {
+ struct sieve_message_part **body_part_idx;
+ struct message_header_line *hdr = block.hdr;
+ struct sieve_message_header *header;
+ unsigned char *data;
+
+ if ( block.part != prev_mpart ) {
+ bool message_rfc822 = FALSE;
+
+ /* Save previous body part */
+ if ( body_part != NULL ) {
+ /* Treat message/rfc822 separately; headers become content */
+ if ( block.part->parent == prev_mpart &&
+ strcmp(body_part->content_type, "message/rfc822") == 0 ) {
+ message_rfc822 = TRUE;
+ } else {
+ if ( save_body ) {
+ sieve_message_part_save
+ (renv, buf, body_part, extract_text);
+ }
+ }
+ if ( iter_all && !array_is_created(&body_part->headers) &&
+ array_count(&headers) > 0 ) {
+ p_array_init(&body_part->headers, pool, array_count(&headers));
+ array_copy(&body_part->headers.arr, 0,
+ &headers.arr, 0, array_count(&headers));
+ }
+ }
+
+ /* Start processing next part */
+ body_part_idx = array_idx_get_space
+ (&msgctx->cached_body_parts, idx);
+ if ( *body_part_idx == NULL )
+ *body_part_idx = p_new(pool, struct sieve_message_part, 1);
+ body_part = *body_part_idx;
+ body_part->content_type = "text/plain";
+ if ( iter_all )
+ array_clear(&headers);
+
+ /* Copy tree structure */
+ if ( block.part->context != NULL ) {
+ struct sieve_message_part *epipart =
+ (struct sieve_message_part *)block.part->context;
+ i_assert(epipart != NULL);
+
+ /* multipart epilogue */
+ body_part->content_type = epipart->content_type;
+ body_part->have_body = TRUE;
+ body_part->epilogue = TRUE;
+ save_body = iter_all || _is_wanted_content_type
+ (content_types, body_part->content_type);
+
+ } else {
+ struct sieve_message_part *parent = NULL;
+
+ if ( block.part->parent != NULL ) {
+ body_part->parent = parent =
+ (struct sieve_message_part *)
+ block.part->parent->context;
+ }
+
+ /* new part */
+ block.part->context = (void*)body_part;
+
+ if ( last_part != NULL ) {
+ i_assert( parent != NULL );
+ if ( last_part->parent == parent ) {
+ last_part->next = body_part;
+ } else if (parent->children == NULL) {
+ parent->children = body_part;
+ } else {
+ struct sieve_message_part *child = parent->children;
+ while (child->next != NULL && child != body_part)
+ child = child->next;
+ if (child != body_part)
+ child->next = body_part;
+ }
+ }
+ }
+ last_part = body_part;
+
+ /* If this is message/rfc822 content, retain the enveloping part for
+ * storing headers as content.
+ */
+ if ( message_rfc822 ) {
+ i_assert(idx > 0);
+ body_part_idx = array_idx_modifiable
+ (&msgctx->cached_body_parts, idx-1);
+ header_part = *body_part_idx;
+ } else {
+ header_part = NULL;
+ }
+
+ prev_mpart = block.part;
+ idx++;
+ }
+
+ if ( hdr != NULL || block.size == 0 ) {
+ enum {
+ _HDR_CONTENT_TYPE,
+ _HDR_CONTENT_DISPOSITION,
+ _HDR_OTHER
+ } hdr_field;
+
+ /* Reading headers */
+ i_assert( body_part != NULL );
+
+ /* Decode block */
+ (void)message_decoder_decode_next_block
+ (decoder, &block, &decoded);
+
+ /* Check for end of headers */
+ if ( hdr == NULL ) {
+ /* Save headers for message/rfc822 part */
+ if ( header_part != NULL ) {
+ sieve_message_part_save
+ (renv, buf, header_part, FALSE);
+ header_part = NULL;
+ }
+
+ /* Save bodies only if we have a wanted content-type */
+ save_body = iter_all || _is_wanted_content_type
+ (content_types, body_part->content_type);
+ continue;
+ }
+
+ /* Encountered the empty line that indicates the end of the headers and
+ * the start of the body
+ */
+ if ( hdr->eoh ) {
+ body_part->have_body = TRUE;
+ continue;
+ } else if ( header_part != NULL ) {
+ /* Save message/rfc822 header as part content */
+ if ( hdr->continued ) {
+ buffer_append(buf, hdr->value, hdr->value_len);
+ } else {
+ buffer_append(buf, hdr->name, hdr->name_len);
+ buffer_append(buf, hdr->middle, hdr->middle_len);
+ buffer_append(buf, hdr->value, hdr->value_len);
+ }
+ if ( !hdr->no_newline ) {
+ buffer_append(buf, "\r\n", 2);
+ }
+ }
+
+ if ( strcasecmp(hdr->name, "Content-Type" ) == 0 )
+ hdr_field = _HDR_CONTENT_TYPE;
+ else if ( strcasecmp(hdr->name, "Content-Disposition" ) == 0 )
+ hdr_field = _HDR_CONTENT_DISPOSITION;
+ else if ( iter_all && !array_is_created(&body_part->headers) )
+ hdr_field = _HDR_OTHER;
+ else {
+ /* Not interested in this header */
+ continue;
+ }
+
+ /* Header can have folding whitespace. Acquire the full value before
+ * continuing
+ */
+ if ( hdr->continues ) {
+ hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ if ( iter_all && !array_is_created(&body_part->headers) ) {
+ const unsigned char *value, *vp;
+ size_t vlen;
+
+ /* Add header */
+ header = array_append_space(&headers);
+ header->name = p_strdup(pool, hdr->name);
+
+ /* Trim end of field value (not done by parser) */
+ value = hdr->full_value;
+ vp = value + hdr->full_value_len;
+ while ( vp > value &&
+ (vp[-1] == '\t' || vp[-1] == ' ') )
+ vp--;
+ vlen = (size_t)(vp - value);
+
+ /* Decode MIME encoded-words. */
+ str_truncate(hdr_content, 0);
+ message_header_decode_utf8
+ (value, vlen, hdr_content, NULL);
+ if ( vlen != str_len(hdr_content) ||
+ strncmp(str_c(hdr_content), (const char *)value,
+ vlen) != 0 ) {
+ if ( strlen(str_c(hdr_content)) != str_len(hdr_content) ) {
+ /* replace NULs with spaces */
+ str_replace_nuls(hdr_content);
+ }
+ /* store raw */
+ data = p_malloc(pool, vlen + 1);
+ data[vlen] = '\0';
+ header->value = memcpy(data, value, vlen);
+ header->value_len = vlen;
+ /* store decoded */
+ data = p_malloc(pool, str_len(hdr_content) + 1);
+ data[str_len(hdr_content)] = '\0';
+ header->utf8_value = memcpy(data,
+ str_data(hdr_content), str_len(hdr_content));
+ header->utf8_value_len = str_len(hdr_content);
+ } else {
+ /* raw == decoded */
+ data = p_malloc(pool, vlen + 1);
+ data[vlen] = '\0';
+ header->value = header->utf8_value =
+ memcpy(data, value, vlen);
+ header->value_len = header->utf8_value_len = vlen;
+ }
+
+ if ( hdr_field == _HDR_OTHER )
+ continue;
+ }
+
+ /* Parse the content type from the Content-type header */
+ T_BEGIN {
+ switch ( hdr_field ) {
+ case _HDR_CONTENT_TYPE:
+ body_part->content_type =
+ p_strdup(pool, _parse_content_type(block.hdr));
+ break;
+ case _HDR_CONTENT_DISPOSITION:
+ body_part->content_disposition =
+ p_strdup(pool, _parse_content_disposition(block.hdr));
+ break;
+ default:
+ i_unreached();
+ }
+ } T_END;
+
+ continue;
+ }
+
+ /* Reading body */
+ if ( save_body ) {
+ (void)message_decoder_decode_next_block
+ (decoder, &block, &decoded);
+ buffer_append(buf, decoded.data, decoded.size);
+ }
+ }
+
+ /* even with an empty message there was at least the "end of headers"
+ block, which set the body_part. */
+ i_assert( body_part != NULL );
+
+ /* Save last body part if necessary */
+ if ( header_part != NULL ) {
+ sieve_message_part_save
+ (renv, buf, header_part, FALSE);
+ } else if ( save_body ) {
+ sieve_message_part_save
+ (renv, buf, body_part, extract_text);
+ }
+ if ( iter_all && !array_is_created(&body_part->headers) &&
+ array_count(&headers) > 0 ) {
+ p_array_init(&body_part->headers, pool, array_count(&headers));
+ array_copy(&body_part->headers.arr, 0,
+ &headers.arr, 0, array_count(&headers));
+ }
+
+ /* Try to fill the return_body_parts array once more */
+ have_all = iter_all || sieve_message_body_get_return_parts
+ (renv, content_types, extract_text);
+
+ /* This time, failure is a bug */
+ i_assert(have_all);
+
+ /* Cleanup */
+ (void)message_parser_deinit(&parser, &mparts);
+ message_decoder_deinit(&decoder);
+ buffer_free(&buf);
+
+ /* Return status */
+ if ( input->stream_errno != 0 ) {
+ sieve_runtime_critical(renv, NULL,
+ "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+int sieve_message_body_get_content
+(const struct sieve_runtime_env *renv,
+ const char * const *content_types,
+ struct sieve_message_part_data **parts_r)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ int status;
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, content_types, FALSE, FALSE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return status;
+}
+
+int sieve_message_body_get_text
+(const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r)
+{
+ static const char * const _text_content_types[] =
+ { "application/xhtml+xml", "text", NULL };
+ struct sieve_message_context *msgctx = renv->msgctx;
+ int status;
+
+ /* We currently only support extracting plain text from:
+
+ - text/html -> HTML
+ - application/xhtml+xml -> XHTML
+
+ Other text types are read as is. Any non-text types are skipped.
+ */
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, _text_content_types, TRUE, FALSE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return status;
+}
+
+int sieve_message_body_get_raw
+(const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part_data *return_part;
+ buffer_t *buf;
+
+ if ( msgctx->raw_body == NULL ) {
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ struct istream *input;
+ struct message_size hdr_size, body_size;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ msgctx->raw_body = buf = buffer_create_dynamic
+ (msgctx->context_pool, 1024*64);
+
+ /* Get stream for message */
+ if ( mail_get_stream(mail, &hdr_size, &body_size, &input) < 0 ) {
+ return sieve_runtime_mail_error(renv, mail,
+ "failed to open input message");
+ }
+
+ /* Skip stream to beginning of body */
+ i_stream_skip(input, hdr_size.physical_size);
+
+ /* Read raw message body */
+ while ( (ret=i_stream_read_more(input, &data, &size)) > 0 ) {
+ buffer_append(buf, data, size);
+
+ i_stream_skip(input, size);
+ }
+
+ if ( ret < 0 && input->stream_errno != 0 ) {
+ sieve_runtime_critical(renv, NULL,
+ "failed to read input message",
+ "read(%s) failed: %s",
+ i_stream_get_name(input),
+ i_stream_get_error(input));
+ return SIEVE_EXEC_TEMP_FAILURE;
+ }
+
+ /* Add terminating NUL to the body part buffer */
+ buffer_append_c(buf, '\0');
+
+ } else {
+ buf = msgctx->raw_body;
+ }
+
+ /* Clear result array */
+ array_clear(&msgctx->return_body_parts);
+
+ if ( buf->used > 1 ) {
+ const char *data = (const char *)buf->data;
+ size_t size = buf->used - 1;
+
+ i_assert( data[size] == '\0' );
+
+ /* Add single item to the result */
+ return_part = array_append_space(&msgctx->return_body_parts);
+ return_part->content = data;
+ return_part->size = size;
+ }
+
+ /* Return the array of body items */
+ (void) array_append_space(&msgctx->return_body_parts); /* NULL-terminate */
+ *parts_r = array_idx_modifiable(&msgctx->return_body_parts, 0);
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Message part iterator
+ */
+
+int sieve_message_part_iter_init
+(struct sieve_message_part_iter *iter,
+ const struct sieve_runtime_env *renv)
+{
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+ int status;
+
+ T_BEGIN {
+ /* Fill the return_body_parts array */
+ status = sieve_message_parts_add_missing
+ (renv, NULL, TRUE, TRUE);
+ } T_END;
+
+ /* Check status */
+ if ( status <= 0 )
+ return status;
+
+ i_zero(iter);
+ iter->renv = renv;
+ iter->index = 0;
+ iter->offset = 0;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if (count == 0)
+ iter->root = NULL;
+ else
+ iter->root = parts[0];
+
+ return SIEVE_EXEC_OK;
+}
+
+void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *subtree)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ *subtree = *iter;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( subtree->index >= count)
+ subtree->root = NULL;
+ else
+ subtree->root = parts[subtree->index];
+ subtree->offset = subtree->index;
+}
+
+void sieve_message_part_iter_children(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *child)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ *child = *iter;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( (child->index+1) >= count || parts[child->index]->children == NULL)
+ child->root = NULL;
+ else
+ child->root = parts[child->index++];
+ child->offset = child->index;
+}
+
+struct sieve_message_part *sieve_message_part_iter_current
+(struct sieve_message_part_iter *iter)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+ struct sieve_message_part *const *parts;
+ unsigned int count;
+
+ if ( iter->root == NULL )
+ return NULL;
+
+ parts = array_get(&msgctx->cached_body_parts, &count);
+ if ( iter->index >= count )
+ return NULL;
+ do {
+ if ( parts[iter->index] == iter->root->next )
+ return NULL;
+ if ( parts[iter->index] == iter->root->parent )
+ return NULL;
+ } while ( parts[iter->index]->epilogue && ++iter->index < count );
+ if ( iter->index >= count )
+ return NULL;
+ return parts[iter->index];
+}
+
+struct sieve_message_part *sieve_message_part_iter_next
+(struct sieve_message_part_iter *iter)
+{
+ const struct sieve_runtime_env *renv = iter->renv;
+ struct sieve_message_context *msgctx = renv->msgctx;
+
+ if ( iter->index >= array_count(&msgctx->cached_body_parts) )
+ return NULL;
+ iter->index++;
+
+ return sieve_message_part_iter_current(iter);
+}
+
+void sieve_message_part_iter_reset
+(struct sieve_message_part_iter *iter)
+{
+ iter->index = iter->offset;
+}
+
+/*
+ * MIME header list
+ */
+
+/* Forward declarations */
+
+static int sieve_mime_header_list_next_item
+ (struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r);
+static int sieve_mime_header_list_next_value
+ (struct sieve_stringlist *_strlist, string_t **value_r);
+static void sieve_mime_header_list_reset
+ (struct sieve_stringlist *_strlist);
+
+/* Header list object */
+
+struct sieve_mime_header_list {
+ struct sieve_header_list hdrlist;
+
+ struct sieve_stringlist *field_names;
+
+ struct sieve_message_part_iter part_iter;
+
+ const char *header_name;
+ const struct sieve_message_header *headers;
+ unsigned int headers_index, headers_count;
+
+ bool mime_decode:1;
+ bool children:1;
+};
+
+struct sieve_header_list *sieve_mime_header_list_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ struct sieve_message_part_iter *part_iter,
+ bool mime_decode, bool children)
+{
+ struct sieve_mime_header_list *hdrlist;
+
+ hdrlist = t_new(struct sieve_mime_header_list, 1);
+ hdrlist->hdrlist.strlist.runenv = renv;
+ hdrlist->hdrlist.strlist.exec_status = SIEVE_EXEC_OK;
+ hdrlist->hdrlist.strlist.next_item = sieve_mime_header_list_next_value;
+ hdrlist->hdrlist.strlist.reset = sieve_mime_header_list_reset;
+ hdrlist->hdrlist.next_item = sieve_mime_header_list_next_item;
+ hdrlist->field_names = field_names;
+ hdrlist->mime_decode = mime_decode;
+ hdrlist->children = children;
+
+ sieve_message_part_iter_subtree(part_iter, &hdrlist->part_iter);
+
+ return &hdrlist->hdrlist;
+}
+
+/* MIME list implementation */
+
+static void sieve_mime_header_list_next_name
+(struct sieve_mime_header_list *hdrlist)
+{
+ struct sieve_message_part *mpart;
+
+ sieve_message_part_iter_reset(&hdrlist->part_iter);
+ mpart = sieve_message_part_iter_current(&hdrlist->part_iter);
+
+ if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+ hdrlist->headers = array_get
+ (&mpart->headers, &hdrlist->headers_count);
+ hdrlist->headers_index = 0;
+ }
+}
+
+static int sieve_mime_header_list_next_item
+(struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r)
+{
+ struct sieve_mime_header_list *hdrlist =
+ (struct sieve_mime_header_list *) _hdrlist;
+ const struct sieve_runtime_env *renv = _hdrlist->strlist.runenv;
+
+ if ( name_r != NULL )
+ *name_r = NULL;
+ *value_r = NULL;
+
+ for (;;) {
+ /* Check for end of current header list */
+ if ( hdrlist->headers_count == 0 ||
+ hdrlist->headers_index >= hdrlist->headers_count ) {
+ hdrlist->headers_count = 0;
+ hdrlist->headers_index = 0;
+ hdrlist->headers = NULL;
+ }
+
+ /* Fetch more headers */
+ while ( hdrlist->headers_count == 0 ) {
+ string_t *hdr_item = NULL;
+ int ret;
+
+ if ( hdrlist->header_name != NULL && hdrlist->children ) {
+ struct sieve_message_part *mpart;
+
+ mpart = sieve_message_part_iter_next(&hdrlist->part_iter);
+ if ( mpart != NULL && array_is_created(&mpart->headers) ) {
+ hdrlist->headers = array_get
+ (&mpart->headers, &hdrlist->headers_count);
+ hdrlist->headers_index = 0;
+ }
+ if ( hdrlist->headers_count > 0 ) {
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "moving to next message part");
+ }
+ break;
+ }
+ }
+
+ /* Read next header name from source list */
+ if ( (ret=sieve_stringlist_next_item
+ (hdrlist->field_names, &hdr_item)) <= 0 )
+ return ret;
+
+ hdrlist->header_name = str_c(hdr_item);
+
+ if ( _hdrlist->strlist.trace ) {
+ sieve_runtime_trace(renv, 0,
+ "extracting `%s' headers from message part",
+ str_sanitize(str_c(hdr_item), 80));
+ }
+
+ sieve_mime_header_list_next_name(hdrlist);
+ }
+
+ for ( ; hdrlist->headers_index < hdrlist->headers_count;
+ hdrlist->headers_index++ ) {
+ const struct sieve_message_header *header =
+ &hdrlist->headers[hdrlist->headers_index];
+
+ if ( strcasecmp(header->name, hdrlist->header_name) == 0 ) {
+ if ( name_r != NULL )
+ *name_r = hdrlist->header_name;
+ if ( hdrlist->mime_decode ) {
+ *value_r = t_str_new_const
+ ((const char *)header->utf8_value, header->utf8_value_len);
+ } else {
+ *value_r = t_str_new_const
+ ((const char *)header->value, header->value_len);
+ }
+ hdrlist->headers_index++;
+ return 1;
+ }
+ }
+ }
+
+ i_unreached();
+ return -1;
+}
+
+static int sieve_mime_header_list_next_value
+(struct sieve_stringlist *_strlist, string_t **value_r)
+{
+ struct sieve_header_list *hdrlist =
+ (struct sieve_header_list *) _strlist;
+
+ return sieve_mime_header_list_next_item
+ (hdrlist, NULL, value_r);
+}
+
+static void sieve_mime_header_list_reset
+(struct sieve_stringlist *strlist)
+{
+ struct sieve_mime_header_list *hdrlist =
+ (struct sieve_mime_header_list *) strlist;
+
+ sieve_stringlist_reset(hdrlist->field_names);
+ hdrlist->header_name = NULL;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-message.h b/pigeonhole/src/lib-sieve/sieve-message.h
new file mode 100644
index 0000000..4cb77cc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-message.h
@@ -0,0 +1,280 @@
+#ifndef SIEVE_MESSAGE_H
+#define SIEVE_MESSAGE_H
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-objects.h"
+
+/*
+ * Message transmission
+ */
+
+const char *sieve_message_get_new_id(const struct sieve_instance *svinst);
+
+/*
+ * Message context
+ */
+
+struct sieve_message_context;
+
+struct sieve_message_context *sieve_message_context_create
+ (struct sieve_instance *svinst, struct mail_user *mail_user,
+ const struct sieve_message_data *msgdata);
+void sieve_message_context_ref(struct sieve_message_context *msgctx);
+void sieve_message_context_unref(struct sieve_message_context **msgctx);
+
+void sieve_message_context_reset(struct sieve_message_context *msgctx);
+
+pool_t sieve_message_context_pool
+ (struct sieve_message_context *msgctx) ATTR_PURE;
+void sieve_message_context_time(struct sieve_message_context *msgctx,
+ struct timeval *time);
+
+/* Extension support */
+
+void sieve_message_context_extension_set
+ (struct sieve_message_context *msgctx, const struct sieve_extension *ext,
+ void *context);
+const void *sieve_message_context_extension_get
+ (struct sieve_message_context *msgctx, const struct sieve_extension *ext);
+
+/* Envelope */
+
+const struct smtp_address *sieve_message_get_final_recipient
+ (struct sieve_message_context *msgctx);
+const struct smtp_address *sieve_message_get_orig_recipient
+ (struct sieve_message_context *msgctx);
+
+const struct smtp_address *sieve_message_get_sender
+ (struct sieve_message_context *msgctx);
+
+/* Mail */
+
+struct mail *sieve_message_get_mail
+ (struct sieve_message_context *msgctx);
+
+int sieve_message_substitute
+ (struct sieve_message_context *msgctx, struct istream *input);
+struct edit_mail *sieve_message_edit
+ (struct sieve_message_context *msgctx);
+void sieve_message_snapshot
+ (struct sieve_message_context *msgctx);
+
+/*
+ * Header stringlist
+ */
+
+struct sieve_header_list {
+ struct sieve_stringlist strlist;
+
+ int (*next_item)
+ (struct sieve_header_list *_hdrlist, const char **name_r,
+ string_t **value_r) ATTR_NULL(2);
+};
+
+static inline int sieve_header_list_next_item
+(struct sieve_header_list *hdrlist, const char **name_r,
+ string_t **value_r) ATTR_NULL(2)
+{
+ return hdrlist->next_item(hdrlist, name_r, value_r);
+}
+
+static inline void sieve_header_list_reset
+(struct sieve_header_list *hdrlist)
+{
+ sieve_stringlist_reset(&hdrlist->strlist);
+}
+
+static inline int sieve_header_list_get_length
+(struct sieve_header_list *hdrlist)
+{
+ return sieve_stringlist_get_length(&hdrlist->strlist);
+}
+
+static inline void sieve_header_list_set_trace
+(struct sieve_header_list *hdrlist, bool trace)
+{
+ sieve_stringlist_set_trace(&hdrlist->strlist, trace);
+}
+
+struct sieve_header_list *sieve_message_header_list_create
+ (const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ bool mime_decode);
+
+/*
+ * Message override
+ */
+
+/* Header override object */
+
+struct sieve_message_override_def {
+ struct sieve_object_def obj_def;
+
+ unsigned int sequence;
+
+ /* Context coding */
+
+ bool (*dump_context)
+ (const struct sieve_message_override *svmo,
+ const struct sieve_dumptime_env *denv, sieve_size_t *address);
+ int (*read_context)
+ (const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv, sieve_size_t *address,
+ void **se_context);
+
+ /* Override */
+
+ int (*header_override)
+ (const struct sieve_message_override *svmo,
+ const struct sieve_runtime_env *renv,
+ bool mime_decode, struct sieve_stringlist **headers);
+};
+
+struct sieve_message_override {
+ struct sieve_object object;
+
+ const struct sieve_message_override_def *def;
+
+ void *context;
+};
+
+ARRAY_DEFINE_TYPE(sieve_message_override,
+ struct sieve_message_override);
+
+/*
+ * Message override operand
+ */
+
+#define SIEVE_EXT_DEFINE_MESSAGE_OVERRIDE(SVMO) SIEVE_EXT_DEFINE_OBJECT(SVMO)
+#define SIEVE_EXT_DEFINE_MESSAGE_OVERRIDES(SVMOS) SIEVE_EXT_DEFINE_OBJECTS(SMOS)
+
+#define SIEVE_OPT_MESSAGE_OVERRIDE (-2)
+
+extern const struct sieve_operand_class
+ sieve_message_override_operand_class;
+
+static inline void sieve_opr_message_override_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_message_override_def *seff)
+{
+ sieve_opr_object_emit(sblock, ext, &seff->obj_def);
+}
+
+bool sieve_opr_message_override_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+int sieve_opr_message_override_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ struct sieve_message_override *svmo);
+
+/*
+ * Optional operands
+ */
+
+int sieve_message_opr_optional_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address,
+ signed int *opt_code);
+
+int sieve_message_opr_optional_read
+ (const struct sieve_runtime_env *renv, sieve_size_t *address,
+ signed int *opt_code, int *exec_status,
+ struct sieve_address_part *addrp, struct sieve_match_type *mcht,
+ struct sieve_comparator *cmp,
+ ARRAY_TYPE(sieve_message_override) *svmos);
+
+/*
+ * Message header
+ */
+
+int sieve_message_get_header_fields
+ (const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ ARRAY_TYPE(sieve_message_override) *svmos,
+ bool mime_decode, struct sieve_stringlist **fields_r);
+
+/*
+ * Message part
+ */
+
+struct sieve_message_part;
+
+struct sieve_message_part_data {
+ const char *content_type;
+ const char *content_disposition;
+
+ const char *content;
+ unsigned long size;
+};
+
+struct sieve_message_part *sieve_message_part_parent
+ (struct sieve_message_part *mpart) ATTR_PURE;
+struct sieve_message_part *sieve_message_part_next
+ (struct sieve_message_part *mpart) ATTR_PURE;
+struct sieve_message_part *sieve_message_part_children
+ (struct sieve_message_part *mpart) ATTR_PURE;
+
+const char *sieve_message_part_content_type
+ (struct sieve_message_part *mpart) ATTR_PURE;
+const char *sieve_message_part_content_disposition
+ (struct sieve_message_part *mpart) ATTR_PURE;
+
+int sieve_message_part_get_first_header
+ (struct sieve_message_part *mpart, const char *field,
+ const char **value_r);
+
+void sieve_message_part_get_data
+ (struct sieve_message_part *mpart,
+ struct sieve_message_part_data *data, bool text);
+
+/*
+ * Message body
+ */
+
+int sieve_message_body_get_content
+ (const struct sieve_runtime_env *renv,
+ const char * const *content_types,
+ struct sieve_message_part_data **parts_r);
+int sieve_message_body_get_text
+ (const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r);
+int sieve_message_body_get_raw
+ (const struct sieve_runtime_env *renv,
+ struct sieve_message_part_data **parts_r);
+
+/*
+ * Message part iterator
+ */
+
+struct sieve_message_part_iter {
+ const struct sieve_runtime_env *renv;
+ struct sieve_message_part *root;
+ unsigned int index, offset;
+};
+
+int sieve_message_part_iter_init
+(struct sieve_message_part_iter *iter,
+ const struct sieve_runtime_env *renv);
+void sieve_message_part_iter_subtree(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *subtree);
+void sieve_message_part_iter_children(struct sieve_message_part_iter *iter,
+ struct sieve_message_part_iter *child);
+
+struct sieve_message_part *sieve_message_part_iter_current
+(struct sieve_message_part_iter *iter);
+struct sieve_message_part *sieve_message_part_iter_next
+(struct sieve_message_part_iter *iter);
+
+void sieve_message_part_iter_reset
+(struct sieve_message_part_iter *iter);
+
+/*
+ * MIME header list
+ */
+
+struct sieve_header_list *sieve_mime_header_list_create
+(const struct sieve_runtime_env *renv,
+ struct sieve_stringlist *field_names,
+ struct sieve_message_part_iter *part_iter,
+ bool mime_decode, bool children);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-objects.c b/pigeonhole/src/lib-sieve/sieve-objects.c
new file mode 100644
index 0000000..66fc969
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-objects.c
@@ -0,0 +1,111 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-objects.h"
+
+/*
+ * Object coding
+ */
+
+void sieve_opr_object_emit
+(struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_object_def *obj_def)
+{
+ struct sieve_extension_objects *objs =
+ (struct sieve_extension_objects *) obj_def->operand->interface;
+
+ (void) sieve_operand_emit(sblock, ext, obj_def->operand);
+
+ if ( objs->count > 1 ) {
+ (void) sieve_binary_emit_byte(sblock, obj_def->code);
+ }
+}
+
+bool sieve_opr_object_read_data
+(struct sieve_binary_block *sblock, const struct sieve_operand *operand,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj)
+{
+ const struct sieve_extension_objects *objs;
+ unsigned int obj_code;
+
+ if ( operand == NULL || operand->def->class != opclass )
+ return FALSE;
+
+ objs = (struct sieve_extension_objects *) operand->def->interface;
+ if ( objs == NULL )
+ return FALSE;
+
+ if ( objs->count > 1 ) {
+ if ( !sieve_binary_read_byte(sblock, address, &obj_code) )
+ return FALSE;
+
+ if ( obj_code < objs->count ) {
+ const struct sieve_object_def *const *objects =
+ (const struct sieve_object_def *const *) objs->objects;
+
+ obj->def = objects[obj_code];
+ obj->ext = operand->ext;
+ return TRUE;
+ }
+ }
+
+ obj->def = (const struct sieve_object_def *) objs->objects;
+ obj->ext = operand->ext;
+ return TRUE;
+}
+
+bool sieve_opr_object_read
+(const struct sieve_runtime_env *renv,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj)
+{
+ struct sieve_operand operand;
+
+ if ( !sieve_operand_read(renv->sblock, address, NULL, &operand) ) {
+ return FALSE;
+ }
+
+ return sieve_opr_object_read_data
+ (renv->sblock, &operand, opclass, address, obj);
+}
+
+bool sieve_opr_object_dump
+(const struct sieve_dumptime_env *denv,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj)
+{
+ struct sieve_operand operand;
+ struct sieve_object obj_i;
+ const char *class;
+
+ if ( obj == NULL )
+ obj = &obj_i;
+
+ sieve_code_mark(denv);
+
+ if ( !sieve_operand_read(denv->sblock, address, NULL, &operand) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_opr_object_read_data
+ (denv->sblock, &operand, opclass, address, obj) )
+ return FALSE;
+
+ if ( operand.def->class == NULL )
+ class = "OBJECT";
+ else
+ class = operand.def->class->name;
+
+ sieve_code_dumpf(denv, "%s: %s", class, obj->def->identifier);
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-objects.h b/pigeonhole/src/lib-sieve/sieve-objects.h
new file mode 100644
index 0000000..e3f300f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-objects.h
@@ -0,0 +1,67 @@
+#ifndef SIEVE_OBJECTS_H
+#define SIEVE_OBJECTS_H
+
+/*
+ * Object definition
+ */
+
+struct sieve_object_def {
+ const char *identifier;
+ const struct sieve_operand_def *operand;
+ unsigned int code;
+};
+
+#define SIEVE_OBJECT(_identifier, _operand, _code) \
+ .obj_def = { \
+ .identifier = (_identifier), \
+ .operand = (_operand), \
+ .code = (_code) \
+ }
+
+/*
+ * Object instance
+ */
+
+struct sieve_object {
+ const struct sieve_object_def *def;
+ const struct sieve_extension *ext;
+};
+
+#define SIEVE_OBJECT_DEFAULT(_obj) \
+ { &((_obj).obj_def), NULL }
+
+#define SIEVE_OBJECT_EXTENSION(_obj) \
+ (_obj->object.ext)
+
+#define SIEVE_OBJECT_SET_DEF(_obj, def_value) \
+ STMT_START { \
+ (_obj)->def = def_value; \
+ (_obj)->object.def = &(_obj)->def->obj_def; \
+ } STMT_END
+
+
+/*
+ * Object coding
+ */
+
+void sieve_opr_object_emit
+ (struct sieve_binary_block *sblock, const struct sieve_extension *ext,
+ const struct sieve_object_def *obj_def);
+
+bool sieve_opr_object_read_data
+ (struct sieve_binary_block *sblock, const struct sieve_operand *operand,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj);
+
+bool sieve_opr_object_read
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj);
+
+bool sieve_opr_object_dump
+ (const struct sieve_dumptime_env *denv,
+ const struct sieve_operand_class *opclass, sieve_size_t *address,
+ struct sieve_object *obj);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-parser.c b/pigeonhole/src/lib-sieve/sieve-parser.c
new file mode 100644
index 0000000..ee54994
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-parser.c
@@ -0,0 +1,670 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "failures.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-lexer.h"
+#include "sieve-parser.h"
+#include "sieve-error.h"
+#include "sieve-ast.h"
+
+/*
+ * Forward declarations
+ */
+
+static int
+sieve_parser_recover(struct sieve_parser *parser,
+ enum sieve_token_type end_token);
+
+/*
+ * Parser object
+ */
+
+struct sieve_parser {
+ pool_t pool;
+
+ bool valid;
+
+ struct sieve_script *script;
+
+ struct sieve_error_handler *ehandler;
+
+ const struct sieve_lexer *lexer;
+ struct sieve_ast *ast;
+};
+
+struct sieve_parser *
+sieve_parser_create(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r)
+{
+ struct sieve_parser *parser;
+ const struct sieve_lexer *lexer;
+
+ lexer = sieve_lexer_create(script, ehandler, error_r);
+ if (lexer != NULL) {
+ pool_t pool = pool_alloconly_create("sieve_parser", 4096);
+
+ parser = p_new(pool, struct sieve_parser, 1);
+ parser->pool = pool;
+ parser->valid = TRUE;
+
+ parser->ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ parser->script = script;
+ sieve_script_ref(script);
+
+ parser->lexer = lexer;
+ parser->ast = NULL;
+
+ return parser;
+ }
+
+ return NULL;
+}
+
+void sieve_parser_free(struct sieve_parser **parser)
+{
+ if ((*parser)->ast != NULL)
+ sieve_ast_unref(&(*parser)->ast);
+
+ sieve_lexer_free(&(*parser)->lexer);
+ sieve_script_unref(&(*parser)->script);
+
+ sieve_error_handler_unref(&(*parser)->ehandler);
+
+ pool_unref(&(*parser)->pool);
+
+ *parser = NULL;
+}
+
+/*
+ * Internal error handling
+ */
+
+inline static void ATTR_FORMAT(4, 5)
+sieve_parser_error(struct sieve_parser *parser,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ /* Don't report a parse error if the lexer complained already */
+ if (sieve_lexer_token_type(parser->lexer) != STT_ERROR) {
+ T_BEGIN {
+ params.location = sieve_error_script_location(
+ parser->script,
+ sieve_lexer_token_line(parser->lexer));
+ sieve_logv(parser->ehandler, &params, fmt, args);
+ } T_END;
+ }
+
+ parser->valid = FALSE;
+
+ va_end(args);
+}
+#define sieve_parser_error(parser, ...) \
+ sieve_parser_error(parser, __FILE__, __LINE__, __VA_ARGS__)
+
+/*
+ * Sieve grammar parsing
+ */
+
+/* sieve_parse_arguments():
+
+ Parses both command arguments and sub-tests:
+ arguments = *argument [test / test-list]
+ argument = string-list / number / tag
+ string = quoted-string / multi-line [[implicitly handled in lexer]]
+ string-list = "[" string *("," string) "]" / string ;; if
+ there is only a single string, the brackets are optional
+ test-list = "(" test *("," test) ")"
+ test = identifier arguments
+ */
+static int
+sieve_parse_arguments(struct sieve_parser *parser, struct sieve_ast_node *node,
+ unsigned int depth)
+{
+ const struct sieve_lexer *lexer = parser->lexer;
+ struct sieve_ast_node *test = NULL;
+ bool test_present = TRUE;
+ bool arg_present = TRUE;
+ int result = 1; /* Indicates whether the parser is in a defined, not
+ necessarily error-free state */
+
+ /* Parse arguments */
+ while (arg_present && result > 0) {
+ struct sieve_ast_argument *arg;
+
+ if (!parser->valid &&
+ !sieve_errors_more_allowed(parser->ehandler)) {
+ result = 0;
+ break;
+ }
+
+ switch (sieve_lexer_token_type(lexer)) {
+ /* String list */
+ case STT_LSQUARE:
+ /* Create stinglist object */
+ arg = sieve_ast_argument_stringlist_create(
+ node, sieve_lexer_token_line(parser->lexer));
+ if (arg == NULL) break;
+ sieve_lexer_skip_token(lexer);
+
+ if (sieve_lexer_token_type(lexer) == STT_STRING) {
+ bool add_failed = FALSE;
+
+ /* Add the string to the list */
+ if (!sieve_ast_stringlist_add(
+ arg, sieve_lexer_token_str(lexer),
+ sieve_lexer_token_line(parser->lexer)))
+ add_failed = TRUE;
+ sieve_lexer_skip_token(lexer);
+
+ while (!add_failed &&
+ sieve_lexer_token_type(lexer) == STT_COMMA) {
+ sieve_lexer_skip_token(lexer);
+
+ /* Check parser status */
+ if (!parser->valid &&
+ !sieve_errors_more_allowed(parser->ehandler)) {
+ result = sieve_parser_recover(parser, STT_RSQUARE);
+ break;
+ }
+
+ if (sieve_lexer_token_type(lexer) == STT_STRING) {
+ /* Add the string to the list */
+ if (!sieve_ast_stringlist_add(
+ arg, sieve_lexer_token_str(lexer),
+ sieve_lexer_token_line(parser->lexer)))
+ add_failed = TRUE;
+
+ sieve_lexer_skip_token(lexer);
+ } else {
+ sieve_parser_error(parser,
+ "expecting string after ',' in string list, "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+ result = sieve_parser_recover(parser, STT_RSQUARE);
+ break;
+ }
+ }
+
+ if (add_failed) {
+ sieve_parser_error(parser,
+ "failed to accept more items in string list");
+ return -1;
+ }
+ } else {
+ sieve_parser_error(parser,
+ "expecting string after '[' in string list, "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+ result = sieve_parser_recover(parser, STT_RSQUARE);
+ }
+
+ /* Finish the string list */
+ if (sieve_lexer_token_type(lexer) == STT_RSQUARE) {
+ sieve_lexer_skip_token(lexer);
+ } else {
+ sieve_parser_error(parser,
+ "expecting ',' or end of string list ']', "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+
+ if ((result = sieve_parser_recover(parser, STT_RSQUARE)) > 0)
+ sieve_lexer_skip_token(lexer);
+ }
+ break;
+ /* Single string */
+ case STT_STRING:
+ arg = sieve_ast_argument_string_create(
+ node, sieve_lexer_token_str(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+ break;
+ /* Number */
+ case STT_NUMBER:
+ arg = sieve_ast_argument_number_create(
+ node, sieve_lexer_token_int(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+ break;
+ /* Tag */
+ case STT_TAG:
+ arg = sieve_ast_argument_tag_create(
+ node, sieve_lexer_token_ident(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+ break;
+ /* End of argument list, continue with tests */
+ default:
+ arg_present = FALSE;
+ break;
+ }
+
+ if (arg_present && arg == NULL) {
+ sieve_parser_error(parser,
+ "failed to accept more arguments for command '%s'",
+ node->identifier);
+ return -1;
+ }
+
+ if (sieve_ast_argument_count(node) > SIEVE_MAX_COMMAND_ARGUMENTS) {
+ sieve_parser_error(parser,
+ "too many arguments for command '%s'",
+ node->identifier);
+ return 0;
+ }
+ }
+
+ if (result <= 0)
+ return result; /* Defer recovery to caller */
+
+ /* --> [ test / test-list ]
+ test-list = "(" test *("," test) ")"
+ test = identifier arguments
+ */
+ switch (sieve_lexer_token_type(lexer)) {
+ /* Single test */
+ case STT_IDENTIFIER:
+ if ((depth + 1) > SIEVE_MAX_TEST_NESTING) {
+ sieve_parser_error(parser,
+ "cannot nest tests deeper than %u levels",
+ SIEVE_MAX_TEST_NESTING);
+ return 0;
+ }
+
+ test = sieve_ast_test_create(
+ node, sieve_lexer_token_ident(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+
+ /* Theoretically, test can be NULL */
+ if (test == NULL)
+ break;
+
+ /* Parse test arguments, which may include more tests (recurse) */
+ if (sieve_parse_arguments(parser, test, depth + 1) <= 0) {
+ return 0; /* Defer recovery to caller */
+ }
+
+ break;
+
+ /* Test list */
+ case STT_LBRACKET:
+ sieve_lexer_skip_token(lexer);
+
+ if (depth+1 > SIEVE_MAX_TEST_NESTING) {
+ sieve_parser_error(parser,
+ "cannot nest tests deeper than %u levels",
+ SIEVE_MAX_TEST_NESTING);
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+
+ if (result > 0)
+ sieve_lexer_skip_token(lexer);
+ return result;
+ }
+
+ node->test_list = TRUE;
+
+ /* Test starts with identifier */
+ if (sieve_lexer_token_type(lexer) == STT_IDENTIFIER) {
+ test = sieve_ast_test_create(
+ node, sieve_lexer_token_ident(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+
+ if (test == NULL)
+ break;
+
+ /* Parse test arguments, which may include more tests (recurse) */
+ if ((result = sieve_parse_arguments(parser, test, depth+1)) > 0) {
+
+ /* More tests ? */
+ while (sieve_lexer_token_type(lexer) == STT_COMMA) {
+ sieve_lexer_skip_token(lexer);
+
+ /* Check parser status */
+ if (!parser->valid &&
+ !sieve_errors_more_allowed(parser->ehandler)) {
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+ break;
+ }
+
+ /* Test starts with identifier */
+ if (sieve_lexer_token_type(lexer) == STT_IDENTIFIER) {
+ test = sieve_ast_test_create(
+ node, sieve_lexer_token_ident(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+
+ if (test == NULL)
+ break;
+
+ /* Parse test arguments, which may include more tests (recurse) */
+ if ((result = sieve_parse_arguments(parser, test, depth+1)) <= 0) {
+ if (result < 0)
+ return result;
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+ break;
+ }
+ } else {
+ sieve_parser_error(parser,
+ "expecting test identifier after ',' in test list, "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+ break;
+ }
+ }
+ if (test == NULL)
+ break;
+ } else {
+ if (result < 0)
+ return result;
+
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+ }
+ } else {
+ sieve_parser_error(parser,
+ "expecting test identifier after '(' in test list, "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+
+ result = sieve_parser_recover(parser, STT_RBRACKET);
+ }
+
+ /* The next token should be a ')', indicating the end of the
+ test list
+ --> previous sieve_parser_recover calls try to restore this
+ situation after parse errors.
+ */
+ if (sieve_lexer_token_type(lexer) == STT_RBRACKET) {
+ sieve_lexer_skip_token(lexer);
+ } else {
+ sieve_parser_error(parser,
+ "expecting ',' or end of test list ')', "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+
+ /* Recover function tries to make next token equal to
+ ')'. If it succeeds we need to skip it.
+ */
+ if ((result = sieve_parser_recover(parser, STT_RBRACKET)) > 0)
+ sieve_lexer_skip_token(lexer);
+ }
+ break;
+
+ default:
+ /* Not an error: test / test-list is optional
+ --> any errors are detected by the caller
+ */
+ test_present = FALSE;
+ break;
+ }
+
+ if (test_present && test == NULL) {
+ sieve_parser_error(parser,
+ "failed to accept more tests for command '%s'",
+ node->identifier);
+ return -1;
+ }
+
+ return result;
+}
+
+/* commands = *command
+ command = identifier arguments ( ";" / block )
+ block = "{" commands "}"
+ */
+static int
+sieve_parse_commands(struct sieve_parser *parser, struct sieve_ast_node *block,
+ unsigned int depth)
+{
+ const struct sieve_lexer *lexer = parser->lexer;
+ int result = 1;
+
+ while (result > 0 &&
+ sieve_lexer_token_type(lexer) == STT_IDENTIFIER) {
+ struct sieve_ast_node *command;
+
+ /* Check parser status */
+ if (!parser->valid &&
+ !sieve_errors_more_allowed(parser->ehandler)) {
+ result = sieve_parser_recover(parser, STT_SEMICOLON);
+ break;
+ }
+
+ /* Create command node */
+ command = sieve_ast_command_create(
+ block, sieve_lexer_token_ident(lexer),
+ sieve_lexer_token_line(parser->lexer));
+ sieve_lexer_skip_token(lexer);
+
+ if (command == NULL) {
+ sieve_parser_error(parser,
+ "failed to accept more commands inside the block of command '%s'",
+ block->identifier);
+ return -1;
+ }
+
+ result = sieve_parse_arguments(parser, command, 1);
+
+ /* Check whether the command is properly terminated
+ (i.e. with ; or a new block)
+ */
+ if (result > 0 &&
+ sieve_lexer_token_type(lexer) != STT_SEMICOLON &&
+ sieve_lexer_token_type(lexer) != STT_LCURLY) {
+
+ sieve_parser_error(parser,
+ "expected end of command ';' or the beginning of a compound block '{', "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+ result = 0;
+ }
+
+ /* Try to recover from parse errors to reacquire a defined state
+ */
+ if (result == 0)
+ result = sieve_parser_recover(parser, STT_SEMICOLON);
+
+ /* Don't bother to continue if we are not in a defined state */
+ if (result <= 0)
+ return result;
+
+ switch (sieve_lexer_token_type(lexer)) {
+ /* End of the command */
+ case STT_SEMICOLON:
+ sieve_lexer_skip_token(lexer);
+ break;
+ /* Command has a block {...} */
+ case STT_LCURLY:
+ sieve_lexer_skip_token(lexer);
+
+ /* Check current depth first */
+ if ((depth + 1) > SIEVE_MAX_BLOCK_NESTING) {
+ sieve_parser_error(parser,
+ "cannot nest command blocks deeper than %u levels",
+ SIEVE_MAX_BLOCK_NESTING);
+ result = sieve_parser_recover(parser, STT_RCURLY);
+
+ if (result > 0)
+ sieve_lexer_skip_token(lexer);
+ break;
+ }
+
+ command->block = TRUE;
+
+ if ((result = sieve_parse_commands(parser, command, depth + 1)) > 0) {
+ if (sieve_lexer_token_type(lexer) != STT_RCURLY) {
+ sieve_parser_error(parser,
+ "expected end of compound block '}', "
+ "but found %s",
+ sieve_lexer_token_description(lexer));
+ result = sieve_parser_recover(parser, STT_RCURLY);
+ } else {
+ sieve_lexer_skip_token(lexer);
+ }
+ } else {
+ if (result < 0)
+ return result;
+
+ if ((result = sieve_parser_recover(parser, STT_RCURLY)) > 0)
+ sieve_lexer_skip_token(lexer);
+ }
+
+ break;
+
+ default:
+ /* Recovered previously, so this cannot happen */
+ i_unreached();
+ }
+ }
+
+ return result;
+}
+
+bool sieve_parser_run(struct sieve_parser *parser, struct sieve_ast **ast)
+{
+ if (parser->ast != NULL)
+ sieve_ast_unref(&parser->ast);
+
+ /* Create AST object if none is provided */
+ if (*ast == NULL)
+ *ast = sieve_ast_create(parser->script);
+ else
+ sieve_ast_ref(*ast);
+
+ parser->ast = *ast;
+
+ /* Scan first token */
+ sieve_lexer_skip_token(parser->lexer);
+
+ /* Parse */
+ if (sieve_parse_commands(parser, sieve_ast_root(parser->ast), 1) > 0 &&
+ parser->valid) {
+ /* Parsed right to EOF ? */
+ if (sieve_lexer_token_type(parser->lexer) != STT_EOF) {
+ sieve_parser_error(parser,
+ "unexpected %s found at (the presumed) end of file",
+ sieve_lexer_token_description(parser->lexer));
+ parser->valid = FALSE;
+ }
+ } else {
+ parser->valid = FALSE;
+ }
+
+ /* Clean up AST if parse failed */
+ if (!parser->valid) {
+ parser->ast = NULL;
+ sieve_ast_unref(ast);
+ }
+
+ return parser->valid;
+}
+
+/* Error recovery:
+ To continue parsing after an error it is important to find the next
+ parsible item in the stream. The recover function skips over the remaining
+ garbage after an error. It tries to find the end of the failed syntax
+ structure and takes nesting of structures into account.
+ */
+
+/* Assign useful names to priorities for readability */
+enum sieve_grammatical_prio {
+ SGP_BLOCK = 3,
+ SGP_COMMAND = 2,
+ SGP_TEST_LIST = 1,
+ SGP_STRING_LIST = 0,
+
+ SGP_OTHER = -1
+};
+
+static inline enum sieve_grammatical_prio
+__get_token_priority(enum sieve_token_type token)
+{
+ switch (token) {
+ case STT_LCURLY:
+ case STT_RCURLY:
+ return SGP_BLOCK;
+ case STT_SEMICOLON:
+ return SGP_COMMAND;
+ case STT_LBRACKET:
+ case STT_RBRACKET:
+ return SGP_TEST_LIST;
+ case STT_LSQUARE:
+ case STT_RSQUARE:
+ return SGP_STRING_LIST;
+ default:
+ break;
+ }
+
+ return SGP_OTHER;
+}
+
+static int
+sieve_parser_recover(struct sieve_parser *parser,
+ enum sieve_token_type end_token)
+{
+ /* The tokens that begin/end a specific block/command/list in order
+ of ascending grammatical priority.
+ */
+ static const enum sieve_token_type begin_tokens[4] = {
+ STT_LSQUARE, STT_LBRACKET, STT_NONE, STT_LCURLY };
+ static const enum sieve_token_type end_tokens[4] = {
+ STT_RSQUARE, STT_RBRACKET, STT_SEMICOLON, STT_RCURLY};
+ const struct sieve_lexer *lexer = parser->lexer;
+ int nesting = 1;
+ enum sieve_grammatical_prio end_priority =
+ __get_token_priority(end_token);
+
+ i_assert(end_priority != SGP_OTHER);
+
+ while (sieve_lexer_token_type(lexer) != STT_EOF &&
+ __get_token_priority(sieve_lexer_token_type(lexer))
+ <= end_priority) {
+ if (sieve_lexer_token_type(lexer) ==
+ begin_tokens[end_priority]) {
+ nesting++;
+ sieve_lexer_skip_token(lexer);
+ continue;
+ }
+ if (sieve_lexer_token_type(lexer) ==
+ end_tokens[end_priority]) {
+ nesting--;
+
+ if (nesting == 0) {
+ /* Next character is the end */
+ return 1;
+ }
+ }
+ sieve_lexer_skip_token(lexer);
+ }
+
+ /* Special case: COMMAND */
+ if (end_token == STT_SEMICOLON &&
+ sieve_lexer_token_type(lexer) == STT_LCURLY) {
+ return 1;
+ }
+
+ /* End not found before eof or end of surrounding grammatical structure
+ */
+ return 0;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-parser.h b/pigeonhole/src/lib-sieve/sieve-parser.h
new file mode 100644
index 0000000..dfcb63c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-parser.h
@@ -0,0 +1,17 @@
+#ifndef SIEVE_PARSER_H
+#define SIEVE_PARSER_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+struct sieve_parser;
+
+struct sieve_parser *
+sieve_parser_create(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r);
+void sieve_parser_free(struct sieve_parser **parser);
+bool sieve_parser_run(struct sieve_parser *parser, struct sieve_ast **ast);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-plugins.c b/pigeonhole/src/lib-sieve/sieve-plugins.c
new file mode 100644
index 0000000..c0f32b1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-plugins.c
@@ -0,0 +1,181 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "module-dir.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+
+#include "sieve-common.h"
+#include "sieve-plugins.h"
+
+/*
+ * Types
+ */
+
+typedef void (*sieve_plugin_load_func_t)
+ (struct sieve_instance *svinst, void **context);
+typedef void (*sieve_plugin_unload_func_t)
+ (struct sieve_instance *svinst, void *context);
+
+struct sieve_plugin {
+ struct module *module;
+
+ void *context;
+
+ struct sieve_plugin *next;
+};
+
+/*
+ * Plugin support
+ */
+
+static struct module *sieve_modules = NULL;
+static int sieve_modules_refcount = 0;
+
+static struct module *sieve_plugin_module_find(const char *name)
+{
+ struct module *module;
+
+ module = sieve_modules;
+ while ( module != NULL ) {
+ const char *mod_name;
+
+ /* Strip module names */
+ mod_name = module_get_plugin_name(module);
+
+ if ( strcmp(mod_name, name) == 0 )
+ return module;
+
+ module = module->next;
+ }
+
+ return NULL;
+}
+
+void sieve_plugins_load
+(struct sieve_instance *svinst, const char *path, const char *plugins)
+{
+ struct module *module;
+ struct module_dir_load_settings mod_set;
+ const char **module_names;
+ unsigned int i;
+
+ /* Determine what to load */
+
+ if ( path == NULL && plugins == NULL ) {
+ path = sieve_setting_get(svinst, "sieve_plugin_dir");
+ plugins = sieve_setting_get(svinst, "sieve_plugins");
+ }
+
+ if ( plugins == NULL || *plugins == '\0' )
+ return;
+
+ if ( path == NULL || *path == '\0' )
+ path = MODULEDIR"/sieve";
+
+ i_zero(&mod_set);
+ mod_set.abi_version = PIGEONHOLE_ABI_VERSION;
+ mod_set.require_init_funcs = TRUE;
+ mod_set.debug = FALSE;
+
+ /* Load missing plugin modules */
+
+ sieve_modules = module_dir_load_missing
+ (sieve_modules, path, plugins, &mod_set);
+
+ /* Call plugin load functions for this Sieve instance */
+
+ if ( svinst->plugins == NULL ) {
+ sieve_modules_refcount++;
+ }
+
+ module_names = t_strsplit_spaces(plugins, ", ");
+
+ for (i = 0; module_names[i] != NULL; i++) {
+ /* Allow giving the module names also in non-base form. */
+ module_names[i] = module_file_get_name(module_names[i]);
+ }
+
+ for (i = 0; module_names[i] != NULL; i++) {
+ struct sieve_plugin *plugin;
+ const char *name = module_names[i];
+ sieve_plugin_load_func_t load_func;
+
+ /* Find the module */
+ module = sieve_plugin_module_find(name);
+ i_assert(module != NULL);
+
+ /* Check whether the plugin is already loaded in this instance */
+ plugin = svinst->plugins;
+ while ( plugin != NULL ) {
+ if ( plugin->module == module )
+ break;
+ plugin = plugin->next;
+ }
+
+ /* Skip it if it is loaded already */
+ if ( plugin != NULL )
+ continue;
+
+ /* Create plugin list item */
+ plugin = p_new(svinst->pool, struct sieve_plugin, 1);
+ plugin->module = module;
+
+ /* Call load function */
+ load_func = (sieve_plugin_load_func_t) module_get_symbol
+ (module, t_strdup_printf("%s_load", module->name));
+ if ( load_func != NULL ) {
+ load_func(svinst, &plugin->context);
+ }
+
+ /* Add plugin to the instance */
+ if ( svinst->plugins == NULL )
+ svinst->plugins = plugin;
+ else {
+ struct sieve_plugin *plugin_last;
+
+ plugin_last = svinst->plugins;
+ while ( plugin_last->next != NULL )
+ plugin_last = plugin_last->next;
+
+ plugin_last->next = plugin;
+ }
+ }
+}
+
+void sieve_plugins_unload(struct sieve_instance *svinst)
+{
+ struct sieve_plugin *plugin;
+
+ if ( svinst->plugins == NULL )
+ return;
+
+ /* Call plugin unload functions for this instance */
+
+ plugin = svinst->plugins;
+ while ( plugin != NULL ) {
+ struct module *module = plugin->module;
+ sieve_plugin_unload_func_t unload_func;
+
+ unload_func = (sieve_plugin_unload_func_t)module_get_symbol
+ (module, t_strdup_printf("%s_unload", module->name));
+ if ( unload_func != NULL ) {
+ unload_func(svinst, plugin->context);
+ }
+
+ plugin = plugin->next;
+ }
+
+ /* Physically unload modules */
+
+ i_assert(sieve_modules_refcount > 0);
+
+ if ( --sieve_modules_refcount != 0 )
+ return;
+
+ module_dir_unload(&sieve_modules);
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-plugins.h b/pigeonhole/src/lib-sieve/sieve-plugins.h
new file mode 100644
index 0000000..996af41
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-plugins.h
@@ -0,0 +1,9 @@
+#ifndef SIEVE_PLUGINS_H
+#define SIEVE_PLUGINS_H
+
+#include "sieve-common.h"
+
+void sieve_plugins_load(struct sieve_instance *svinst, const char *path, const char *plugins);
+void sieve_plugins_unload(struct sieve_instance *svinst);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-result.c b/pigeonhole/src/lib-sieve/sieve-result.c
new file mode 100644
index 0000000..89d249e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-result.c
@@ -0,0 +1,2445 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "ostream.h"
+#include "hash.h"
+#include "str.h"
+#include "llist.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "var-expand.h"
+#include "message-address.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-script.h"
+#include "sieve-error.h"
+#include "sieve-interpreter.h"
+#include "sieve-actions.h"
+#include "sieve-message.h"
+
+#include "sieve-result.h"
+
+#include <stdio.h>
+
+struct event_category event_category_sieve_action = {
+ .parent = &event_category_sieve,
+ .name = "sieve-action",
+};
+
+/*
+ * Types
+ */
+
+enum sieve_action_execution_state {
+ SIEVE_ACTION_EXECUTION_STATE_INIT = 0,
+ SIEVE_ACTION_EXECUTION_STATE_STARTED,
+ SIEVE_ACTION_EXECUTION_STATE_EXECUTED,
+ SIEVE_ACTION_EXECUTION_STATE_FINALIZED,
+};
+
+struct sieve_result_action {
+ struct sieve_action action;
+
+ struct sieve_side_effects_list *seffects;
+
+ struct sieve_result_action *prev, *next;
+};
+
+struct sieve_side_effects_list {
+ struct sieve_result *result;
+
+ struct sieve_result_side_effect *first_effect;
+ struct sieve_result_side_effect *last_effect;
+};
+
+struct sieve_result_side_effect {
+ struct sieve_side_effect seffect;
+
+ struct sieve_result_side_effect *prev, *next;
+};
+
+struct sieve_result_action_context {
+ const struct sieve_action_def *action;
+ struct sieve_side_effects_list *seffects;
+};
+
+/*
+ * Result object
+ */
+
+struct sieve_result {
+ pool_t pool;
+ int refcount;
+
+ struct sieve_instance *svinst;
+ struct event *event;
+
+ /* Context data for extensions */
+ ARRAY(void *) ext_contexts;
+
+ const struct sieve_execute_env *exec_env;
+ struct sieve_error_handler *ehandler;
+ struct sieve_message_context *msgctx;
+
+ unsigned int exec_seq;
+ struct sieve_result_execution *exec;
+
+ struct sieve_action keep_action;
+ struct sieve_action failure_action;
+
+ unsigned int action_count;
+ struct sieve_result_action *actions_head, *actions_tail;
+
+ HASH_TABLE(const struct sieve_action_def *,
+ struct sieve_result_action_context *) action_contexts;
+};
+
+static const char *
+sieve_result_event_log_message(struct sieve_result *result,
+ enum log_type log_type, const char *message)
+{
+ const struct sieve_script_env *senv = result->exec_env->scriptenv;
+
+ i_assert(senv->result_amend_log_message != NULL);
+ return senv->result_amend_log_message(senv, log_type, message);
+}
+
+struct sieve_result *
+sieve_result_create(struct sieve_instance *svinst, pool_t pool,
+ const struct sieve_execute_env *eenv)
+{
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct sieve_message_data *msgdata = eenv->msgdata;
+ struct sieve_result *result;
+
+ pool_ref(pool);
+
+ result = p_new(pool, struct sieve_result, 1);
+ result->refcount = 1;
+ result->pool = pool;
+ result->svinst = svinst;
+
+ result->event = event_create(eenv->event);
+ event_add_category(result->event, &event_category_sieve_action);
+ if (senv->result_amend_log_message != NULL) {
+ event_set_log_message_callback(
+ result->event, sieve_result_event_log_message, result);
+ }
+
+ p_array_init(&result->ext_contexts, pool, 4);
+
+ result->exec_env = eenv;
+ result->msgctx =
+ sieve_message_context_create(svinst, senv->user, msgdata);
+
+ result->keep_action.def = &act_store;
+ result->keep_action.ext = NULL;
+ result->failure_action.def = &act_store;
+ result->failure_action.ext = NULL;
+
+ result->action_count = 0;
+ result->actions_head = NULL;
+ result->actions_tail = NULL;
+
+ return result;
+}
+
+void sieve_result_ref(struct sieve_result *result)
+{
+ result->refcount++;
+}
+
+static void
+sieve_result_action_deinit(struct sieve_result_action *ract)
+{
+ event_unref(&ract->action.event);
+}
+
+void sieve_result_unref(struct sieve_result **_result)
+{
+ struct sieve_result *result = *_result;
+ struct sieve_result_action *ract;
+
+ i_assert(result->refcount > 0);
+
+ if (--result->refcount != 0)
+ return;
+
+ sieve_message_context_unref(&result->msgctx);
+
+ hash_table_destroy(&result->action_contexts);
+
+ ract = result->actions_head;
+ while (ract != NULL) {
+ sieve_result_action_deinit(ract);
+ ract = ract->next;
+ }
+ event_unref(&result->event);
+
+ pool_unref(&result->pool);
+ *_result = NULL;
+}
+
+pool_t sieve_result_pool(struct sieve_result *result)
+{
+ return result->pool;
+}
+
+/*
+ * Getters/Setters
+ */
+
+const struct sieve_script_env *
+sieve_result_get_script_env(struct sieve_result *result)
+{
+ return result->exec_env->scriptenv;
+}
+
+const struct sieve_message_data *
+sieve_result_get_message_data(struct sieve_result *result)
+{
+ return result->exec_env->msgdata;
+}
+
+struct sieve_message_context *
+sieve_result_get_message_context(struct sieve_result *result)
+{
+ return result->msgctx;
+}
+
+unsigned int sieve_result_get_exec_seq(struct sieve_result *result)
+{
+ return result->exec_seq;
+}
+
+/*
+ * Extension support
+ */
+
+void sieve_result_extension_set_context(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ if (ext->id < 0)
+ return;
+
+ array_idx_set(&result->ext_contexts, (unsigned int) ext->id, &context);
+}
+
+const void *
+sieve_result_extension_get_context(struct sieve_result *result,
+ const struct sieve_extension *ext)
+{
+ void * const *ctx;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&result->ext_contexts))
+ return NULL;
+
+ ctx = array_idx(&result->ext_contexts, (unsigned int) ext->id);
+
+ return *ctx;
+}
+
+/*
+ * Result composition
+ */
+
+static void
+sieve_result_init_action_event(struct sieve_result *result,
+ struct sieve_action *action, bool add_prefix)
+{
+ const char *name = sieve_action_name(action);
+
+ if (action->event != NULL)
+ return;
+
+ action->event = event_create(result->event);
+ if (add_prefix && name != NULL) {
+ event_set_append_log_prefix(
+ action->event, t_strconcat(name, " action: ", NULL));
+ }
+ event_add_str(action->event, "action_name", name);
+ event_add_str(action->event, "script_location", action->location);
+}
+
+void sieve_result_add_implicit_side_effect(
+ struct sieve_result *result, const struct sieve_action_def *to_action,
+ bool to_keep, const struct sieve_extension *ext,
+ const struct sieve_side_effect_def *seff_def, void *context)
+{
+ struct sieve_result_action_context *actctx = NULL;
+ struct sieve_side_effect seffect;
+
+ to_action = to_keep ? &act_store : to_action;
+
+ if (!hash_table_is_created(result->action_contexts)) {
+ hash_table_create_direct(&result->action_contexts,
+ result->pool, 0);
+ } else {
+ actctx = hash_table_lookup(result->action_contexts, to_action);
+ }
+
+ if (actctx == NULL) {
+ actctx = p_new(result->pool,
+ struct sieve_result_action_context, 1);
+ actctx->action = to_action;
+ actctx->seffects = sieve_side_effects_list_create(result);
+
+ hash_table_insert(result->action_contexts, to_action, actctx);
+ }
+
+ seffect.object.def = &seff_def->obj_def;
+ seffect.object.ext = ext;
+ seffect.def = seff_def;
+ seffect.context = context;
+
+ sieve_side_effects_list_add(actctx->seffects, &seffect);
+}
+
+static int
+sieve_result_side_effects_merge(const struct sieve_runtime_env *renv,
+ const struct sieve_action *action,
+ struct sieve_result_action *old_action,
+ struct sieve_side_effects_list *new_seffects)
+{
+ struct sieve_side_effects_list *old_seffects = old_action->seffects;
+ int ret;
+ struct sieve_result_side_effect *rsef, *nrsef;
+
+ /* Allow side-effects to merge with existing copy */
+
+ /* Merge existing side effects */
+ rsef = old_seffects != NULL ? old_seffects->first_effect : NULL;
+ while (rsef != NULL) {
+ struct sieve_side_effect *seffect = &rsef->seffect;
+ bool found = FALSE;
+
+ i_assert(seffect->def != NULL);
+ if (seffect->def->merge != NULL) {
+ /* Try to find it among the new */
+ nrsef = (new_seffects != NULL ?
+ new_seffects->first_effect : NULL);
+ while (nrsef != NULL) {
+ struct sieve_side_effect *nseffect = &nrsef->seffect;
+
+ if (nseffect->def == seffect->def) {
+ if (seffect->def->merge(
+ renv, action, seffect, nseffect,
+ &seffect->context) < 0)
+ return -1;
+
+ found = TRUE;
+ break;
+ }
+ nrsef = nrsef->next;
+ }
+
+ /* Not found? */
+ if (!found && seffect->def->merge(
+ renv, action, seffect, NULL,
+ &rsef->seffect.context) < 0)
+ return -1;
+ }
+ rsef = rsef->next;
+ }
+
+ /* Merge new Side effects */
+ nrsef = new_seffects != NULL ? new_seffects->first_effect : NULL;
+ while (nrsef != NULL) {
+ struct sieve_side_effect *nseffect = &nrsef->seffect;
+ bool found = FALSE;
+
+ i_assert(nseffect->def != NULL);
+ if (nseffect->def->merge != NULL) {
+ /* Try to find it among the exising */
+ rsef = (old_seffects != NULL ?
+ old_seffects->first_effect : NULL);
+ while (rsef != NULL) {
+ if (rsef->seffect.def == nseffect->def) {
+ found = TRUE;
+ break;
+ }
+ rsef = rsef->next;
+ }
+
+ /* Not found? */
+ if (!found) {
+ void *new_context = NULL;
+
+ if ((ret = nseffect->def->merge(
+ renv, action, nseffect, nseffect,
+ &new_context)) < 0)
+ return -1;
+
+ if (ret != 0) {
+ if (old_action->seffects == NULL) {
+ old_action->seffects = old_seffects =
+ sieve_side_effects_list_create(renv->result);
+ }
+
+ nseffect->context = new_context;
+
+ /* Add side effect */
+ sieve_side_effects_list_add(old_seffects,
+ nseffect);
+ }
+ }
+ }
+ nrsef = nrsef->next;
+ }
+
+ return 1;
+}
+
+static void
+sieve_result_action_detach(struct sieve_result *result,
+ struct sieve_result_action *raction)
+{
+ if (result->actions_head == raction)
+ result->actions_head = raction->next;
+
+ if (result->actions_tail == raction)
+ result->actions_tail = raction->prev;
+
+ if (raction->next != NULL)
+ raction->next->prev = raction->prev;
+ if (raction->prev != NULL)
+ raction->prev->next = raction->next;
+
+ raction->next = NULL;
+ raction->prev = NULL;
+
+ if (result->action_count > 0)
+ result->action_count--;
+}
+
+static int
+_sieve_result_add_action(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext, const char *name,
+ const struct sieve_action_def *act_def,
+ struct sieve_side_effects_list *seffects,
+ void *context, unsigned int instance_limit,
+ bool preserve_mail, bool keep)
+{
+ int ret = 0;
+ unsigned int instance_count = 0;
+ struct sieve_instance *svinst = renv->exec_env->svinst;
+ struct sieve_result *result = renv->result;
+ struct sieve_result_action *raction = NULL, *kaction = NULL;
+ struct sieve_action action;
+
+ i_assert(name != NULL || act_def != NULL);
+ action.name = name;
+ action.def = act_def;
+ action.ext = ext;
+ action.location = sieve_runtime_get_full_command_location(renv);
+ action.context = context;
+ action.exec_seq = result->exec_seq;
+
+ /* First, check for duplicates or conflicts */
+ raction = result->actions_head;
+ while (raction != NULL) {
+ const struct sieve_action *oact = &raction->action;
+ bool oact_new = (oact->exec_seq == result->exec_seq);
+
+ if (keep && raction->action.keep) {
+ /* Duplicate keep */
+ if (oact->def == NULL || !oact_new) {
+ /* Keep action from preceeding execution */
+
+ /* Detach existing keep action */
+ sieve_result_action_detach(result, raction);
+
+ /* Merge existing side-effects with new keep action */
+ if (kaction == NULL)
+ kaction = raction;
+
+ if ((ret = sieve_result_side_effects_merge(
+ renv, &action, kaction, seffects)) <= 0)
+ return ret;
+ } else {
+ /* True duplicate */
+ return sieve_result_side_effects_merge(
+ renv, &action, raction, seffects);
+ }
+ } else if ( act_def != NULL && raction->action.def == act_def ) {
+ instance_count++;
+
+ /* Possible duplicate */
+ if (act_def->check_duplicate != NULL) {
+ if ((ret = act_def->check_duplicate(
+ renv, &action, &raction->action)) < 0)
+ return ret;
+
+ /* Duplicate */
+ if (ret == 1) {
+ if (keep && !oact->keep) {
+ /* New keep has higher precedence than
+ existing duplicate non-keep action.
+ So, take over the result action object
+ and transform it into a keep.
+ */
+ if ((ret = sieve_result_side_effects_merge(
+ renv, &action, raction, seffects)) < 0)
+ return ret;
+
+ if (kaction == NULL) {
+ raction->action.context = NULL;
+ raction->action.location =
+ p_strdup(result->pool, action.location);
+
+ /* Note that existing execution
+ status is retained, making sure
+ that keep is not executed
+ multiple times.
+ */
+ kaction = raction;
+ } else {
+ sieve_result_action_detach(result, raction);
+
+ if ((ret = sieve_result_side_effects_merge(
+ renv, &action, kaction,
+ raction->seffects)) < 0)
+ return ret;
+ }
+ } else {
+ /* Merge side-effects, but don't add new action
+ */
+ return sieve_result_side_effects_merge(
+ renv, &action, raction, seffects);
+ }
+ }
+ }
+ } else {
+ if (act_def != NULL && oact->def != NULL) {
+ /* Check conflict */
+ if (act_def->check_conflict != NULL &&
+ (ret = act_def->check_conflict(
+ renv, &action, &raction->action)) != 0)
+ return ret;
+
+ if (oact_new &&
+ oact->def->check_conflict != NULL &&
+ (ret = oact->def->check_conflict(
+ renv, &raction->action, &action)) != 0)
+ return ret;
+ }
+ }
+ raction = raction->next;
+ }
+
+ if (kaction != NULL) {
+ /* Use existing keep action to define new one */
+ raction = kaction;
+ } else {
+ /* Check policy limit on total number of actions */
+ if (svinst->max_actions > 0 &&
+ result->action_count >= svinst->max_actions)
+ {
+ sieve_runtime_error(
+ renv, action.location,
+ "total number of actions exceeds policy limit "
+ "(%u > %u)",
+ result->action_count+1, svinst->max_actions);
+ return -1;
+ }
+
+ /* Check policy limit on number of this class of actions */
+ if (instance_limit > 0 && instance_count >= instance_limit) {
+ sieve_runtime_error(
+ renv, action.location,
+ "number of %s actions exceeds policy limit "
+ "(%u > %u)",
+ act_def->name, instance_count+1,
+ instance_limit);
+ return -1;
+ }
+
+ /* Create new action object */
+ raction = p_new(result->pool, struct sieve_result_action, 1);
+ raction->seffects = seffects;
+ }
+
+ raction->action.name = (action.name == NULL ?
+ act_def->name :
+ p_strdup(result->pool, action.name));
+ raction->action.context = context;
+ raction->action.def = act_def;
+ raction->action.ext = ext;
+ raction->action.location = p_strdup(result->pool, action.location);
+ raction->action.keep = keep;
+ raction->action.exec_seq = result->exec_seq;
+
+ if (raction->prev == NULL && raction != result->actions_head) {
+ /* Add */
+ if (result->actions_head == NULL) {
+ result->actions_head = raction;
+ result->actions_tail = raction;
+ raction->prev = NULL;
+ raction->next = NULL;
+ } else {
+ result->actions_tail->next = raction;
+ raction->prev = result->actions_tail;
+ result->actions_tail = raction;
+ raction->next = NULL;
+ }
+ result->action_count++;
+
+ /* Apply any implicit side effects */
+ if (hash_table_is_created(result->action_contexts)) {
+ struct sieve_result_action_context *actctx;
+
+ /* Check for implicit side effects to this particular
+ action */
+ actctx = hash_table_lookup(
+ result->action_contexts,
+ (keep ? &act_store : act_def));
+
+ if (actctx != NULL) {
+ struct sieve_result_side_effect *iseff;
+
+ /* Iterate through all implicit side effects and
+ add those that are missing.
+ */
+ iseff = actctx->seffects->first_effect;
+ while (iseff != NULL) {
+ struct sieve_result_side_effect *seff;
+ bool exists = FALSE;
+
+ /* Scan for presence */
+ if (seffects != NULL) {
+ seff = seffects->first_effect;
+ while (seff != NULL) {
+ if (seff->seffect.def == iseff->seffect.def) {
+ exists = TRUE;
+ break;
+ }
+
+ seff = seff->next;
+ }
+ } else {
+ raction->seffects = seffects =
+ sieve_side_effects_list_create(result);
+ }
+
+ /* If not present, add it */
+ if (!exists) {
+ sieve_side_effects_list_add(seffects, &iseff->seffect);
+ }
+
+ iseff = iseff->next;
+ }
+ }
+ }
+ }
+
+ if (preserve_mail) {
+ raction->action.mail = sieve_message_get_mail(renv->msgctx);
+ sieve_message_snapshot(renv->msgctx);
+ } else {
+ raction->action.mail = NULL;
+ }
+
+ sieve_result_init_action_event(result, &raction->action, !keep);
+ return 0;
+}
+
+int sieve_result_add_action(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext, const char *name,
+ const struct sieve_action_def *act_def,
+ struct sieve_side_effects_list *seffects,
+ void *context, unsigned int instance_limit,
+ bool preserve_mail)
+{
+ return _sieve_result_add_action(renv, ext, name, act_def, seffects,
+ context, instance_limit, preserve_mail,
+ FALSE);
+}
+
+int sieve_result_add_keep(const struct sieve_runtime_env *renv,
+ struct sieve_side_effects_list *seffects)
+{
+ return _sieve_result_add_action(renv, renv->result->keep_action.ext,
+ "keep", renv->result->keep_action.def,
+ seffects, NULL, 0, TRUE, TRUE);
+}
+
+void sieve_result_set_keep_action(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ const struct sieve_action_def *act_def)
+{
+ result->keep_action.def = act_def;
+ result->keep_action.ext = ext;
+}
+
+void sieve_result_set_failure_action(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ const struct sieve_action_def *act_def)
+{
+ result->failure_action.def = act_def;
+ result->failure_action.ext = ext;
+}
+
+/*
+ * Result printing
+ */
+
+void sieve_result_vprintf(const struct sieve_result_print_env *penv,
+ const char *fmt, va_list args)
+{
+ string_t *outbuf = t_str_new(128);
+
+ str_vprintfa(outbuf, fmt, args);
+
+ o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_result_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_result_vprintf(penv, fmt, args);
+ va_end(args);
+}
+
+void sieve_result_action_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...)
+{
+ string_t *outbuf = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_append(outbuf, " * ");
+ str_vprintfa(outbuf, fmt, args);
+ str_append_c(outbuf, '\n');
+ va_end(args);
+
+ o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+void sieve_result_seffect_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...)
+{
+ string_t *outbuf = t_str_new(128);
+ va_list args;
+
+ va_start(args, fmt);
+ str_append(outbuf, " + ");
+ str_vprintfa(outbuf, fmt, args);
+ str_append_c(outbuf, '\n');
+ va_end(args);
+
+ o_stream_nsend(penv->stream, str_data(outbuf), str_len(outbuf));
+}
+
+static void
+sieve_result_print_side_effects(struct sieve_result_print_env *rpenv,
+ const struct sieve_action *action,
+ struct sieve_side_effects_list *slist,
+ bool *implicit_keep)
+{
+ struct sieve_result_side_effect *rsef;
+
+ /* Print side effects */
+ rsef = (slist != NULL ? slist->first_effect : NULL);
+ while (rsef != NULL) {
+ const struct sieve_side_effect *sef = &rsef->seffect;
+
+ i_assert(sef->def != NULL);
+
+ if (sef->def->print != NULL) {
+ sef->def->print(sef, action, rpenv,
+ implicit_keep);
+ }
+ rsef = rsef->next;
+ }
+}
+
+static void
+sieve_result_print_implicit_side_effects(struct sieve_result_print_env *rpenv)
+{
+ struct sieve_result *result = rpenv->result;
+ bool dummy = TRUE;
+
+ /* Print any implicit side effects if applicable */
+ if (hash_table_is_created(result->action_contexts)) {
+ struct sieve_result_action_context *actctx;
+
+ /* Check for implicit side effects to keep action */
+ actctx = hash_table_lookup(rpenv->result->action_contexts,
+ &act_store);
+
+ if (actctx != NULL && actctx->seffects != NULL) {
+ sieve_result_print_side_effects(
+ rpenv, &result->keep_action,
+ actctx->seffects, &dummy);
+ }
+ }
+}
+
+bool sieve_result_print(struct sieve_result *result,
+ const struct sieve_script_env *senv,
+ struct ostream *stream, bool *keep)
+{
+ struct sieve_action act_keep = result->keep_action;
+ struct sieve_result_print_env penv;
+ bool implicit_keep = TRUE, printed_any = FALSE;
+ struct sieve_result_action *rac;
+
+ if (keep != NULL)
+ *keep = FALSE;
+
+ /* Prepare environment */
+
+ penv.result = result;
+ penv.stream = stream;
+ penv.scriptenv = senv;
+
+ sieve_result_printf(&penv, "\nPerformed actions:\n\n");
+
+ rac = result->actions_head;
+ while (rac != NULL) {
+ bool impl_keep = TRUE;
+ const struct sieve_action *act = &rac->action;
+
+ if (act->exec_seq < result->exec_seq) {
+ rac = rac->next;
+ continue;
+ }
+
+ if (rac->action.keep && keep != NULL)
+ *keep = TRUE;
+
+ if (act->def != NULL) {
+ if (act->def->print != NULL)
+ act->def->print(act, &penv, &impl_keep);
+ else {
+ sieve_result_action_printf(
+ &penv, "%s", act->def->name);
+ }
+ } else {
+ if (act->keep) {
+ sieve_result_action_printf(&penv, "keep");
+ impl_keep = FALSE;
+ } else {
+ sieve_result_action_printf(&penv, "[NULL]");
+ }
+ }
+ printed_any = TRUE;
+
+ /* Print side effects */
+ sieve_result_print_side_effects(
+ &penv, &rac->action, rac->seffects, &impl_keep);
+
+ implicit_keep = implicit_keep && impl_keep;
+
+ rac = rac->next;
+ }
+ if (!printed_any)
+ sieve_result_printf(&penv, " (none)\n");
+
+ if (implicit_keep && keep != NULL)
+ *keep = TRUE;
+
+ sieve_result_printf(&penv, "\nImplicit keep:\n\n");
+
+ if (implicit_keep) {
+ bool dummy = TRUE;
+
+ if (act_keep.def == NULL) {
+ sieve_result_action_printf(&penv, "keep");
+
+ sieve_result_print_implicit_side_effects(&penv);
+ } else {
+ /* Scan for execution of keep-equal actions */
+ rac = result->actions_head;
+ while (act_keep.def != NULL && rac != NULL) {
+ if (rac->action.def == act_keep.def &&
+ act_keep.def->equals != NULL &&
+ act_keep.def->equals(senv, NULL, &rac->action) &&
+ sieve_action_is_executed(&rac->action,
+ result))
+ act_keep.def = NULL;
+
+ rac = rac->next;
+ }
+
+ if (act_keep.def == NULL) {
+ sieve_result_printf(&penv,
+ " (none; keep or equivalent action executed earlier)\n");
+ } else {
+ act_keep.def->print(&act_keep, &penv, &dummy);
+
+ sieve_result_print_implicit_side_effects(&penv);
+ }
+ }
+ } else {
+ sieve_result_printf(&penv, " (none)\n");
+ }
+
+ sieve_result_printf(&penv, "\n");
+
+ return TRUE;
+}
+
+/*
+ * Result execution
+ */
+
+struct sieve_side_effect_execution {
+ struct sieve_result_side_effect *seffect;
+
+ void *tr_context;
+
+ struct sieve_side_effect_execution *prev, *next;
+};
+
+struct sieve_action_execution {
+ struct sieve_result_action *action;
+ unsigned int exec_seq;
+ struct sieve_action_execution *prev, *next;
+
+ struct sieve_side_effect_execution *seffects_head, *seffects_tail;
+
+ struct sieve_error_handler *ehandler;
+ void *tr_context;
+ enum sieve_action_execution_state state;
+ int status;
+
+ bool commit:1;
+};
+
+struct sieve_result_execution {
+ pool_t pool;
+ struct sieve_action_exec_env action_env;
+ struct sieve_error_handler *ehandler;
+ struct event *event;
+
+ int status;
+
+ struct sieve_action_execution *actions_head, *actions_tail;
+
+ struct sieve_result_action keep_action;
+ struct sieve_action_execution keep;
+ struct sieve_action_execution *keep_equiv_action;
+ int keep_status;
+
+ bool keep_success:1;
+ bool keep_explicit:1;
+ bool keep_implicit:1;
+ bool keep_finalizing:1;
+ bool seen_delivery:1;
+ bool executed:1;
+ bool executed_delivery:1;
+ bool committed:1;
+};
+
+void sieve_result_mark_executed(struct sieve_result *result)
+{
+ result->exec_seq++;
+}
+
+/* Side effect */
+
+static int
+sieve_result_side_effect_pre_execute(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_side_effect_execution *seexec)
+{
+ struct sieve_result_side_effect *rsef = seexec->seffect;
+ struct sieve_side_effect *sef = &rsef->seffect;
+
+ i_assert(sef->def != NULL);
+ if (sef->def->pre_execute == NULL)
+ return SIEVE_EXEC_OK;
+
+ return sef->def->pre_execute(sef, &rexec->action_env,
+ aexec->tr_context, &seexec->tr_context);
+}
+
+static int
+sieve_result_side_effect_post_execute(
+ struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_side_effect_execution *seexec, bool *impl_keep)
+{
+ struct sieve_result_side_effect *rsef = seexec->seffect;
+ struct sieve_side_effect *sef = &rsef->seffect;
+
+ i_assert(sef->def != NULL);
+ if (sef->def->post_execute == NULL)
+ return SIEVE_EXEC_OK;
+
+ return sef->def->post_execute(sef, &rexec->action_env,
+ aexec->tr_context, seexec->tr_context,
+ impl_keep);
+}
+
+static void
+sieve_result_side_effect_post_commit(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_side_effect_execution *seexec,
+ int commit_status)
+{
+ struct sieve_result_side_effect *rsef = seexec->seffect;
+ struct sieve_side_effect *sef = &rsef->seffect;
+
+ i_assert(sef->def != NULL);
+ if (sef->def->post_commit == NULL)
+ return;
+
+ sef->def->post_commit(sef, &rexec->action_env,
+ aexec->tr_context, seexec->tr_context,
+ commit_status);
+}
+
+static void
+sieve_result_side_effect_rollback(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_side_effect_execution *seexec)
+{
+ struct sieve_result_side_effect *rsef = seexec->seffect;
+ struct sieve_side_effect *sef = &rsef->seffect;
+
+ i_assert(sef->def != NULL);
+ if (sef->def->rollback == NULL)
+ return;
+
+ sef->def->rollback(sef, &rexec->action_env,
+ aexec->tr_context, seexec->tr_context,
+ (aexec->status == SIEVE_EXEC_OK));
+}
+
+static void
+sieve_action_execution_add_side_effect(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_result_side_effect *seffect)
+{
+ struct sieve_side_effect_execution *seexec;
+
+ seexec = aexec->seffects_head;
+ while (seexec != NULL) {
+ if (seexec->seffect == seffect)
+ return;
+ seexec = seexec->next;
+ }
+
+ seexec = p_new(rexec->pool, struct sieve_side_effect_execution, 1);
+ seexec->seffect = seffect;
+
+ DLLIST2_APPEND(&aexec->seffects_head, &aexec->seffects_tail, seexec);
+}
+
+static void
+sieve_action_execution_add_side_effects(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ struct sieve_result_action *rac)
+{
+ struct sieve_result_side_effect *rsef;
+
+ rsef = (rac->seffects == NULL ? NULL : rac->seffects->first_effect);
+ while (rsef != NULL) {
+ sieve_action_execution_add_side_effect(rexec, aexec, rsef);
+ rsef = rsef->next;
+ }
+}
+
+/* Action */
+
+static void
+sieve_action_execution_pre(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ if (aexec->ehandler == NULL)
+ aexec->ehandler = rexec->ehandler;
+ rexec->action_env.action = &aexec->action->action;
+ rexec->action_env.event = aexec->action->action.event;
+ rexec->action_env.ehandler = aexec->ehandler;
+}
+
+static void
+sieve_action_execution_post(struct sieve_result_execution *rexec)
+{
+ rexec->action_env.action = NULL;
+ rexec->action_env.event = rexec->action_env.result->event;
+ rexec->action_env.ehandler = NULL;
+}
+
+static int
+sieve_result_action_start(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+ int status = SIEVE_EXEC_OK;
+
+ /* Skip actions that are already started. */
+ if (aexec->state >= SIEVE_ACTION_EXECUTION_STATE_STARTED)
+ return status;
+ aexec->state = SIEVE_ACTION_EXECUTION_STATE_STARTED;
+ aexec->status = status;
+
+ /* Skip non-actions (inactive keep). */
+ if (act->def == NULL)
+ return status;
+
+ if (act->def->start != NULL) {
+ sieve_action_execution_pre(rexec, aexec);
+ status = act->def->start(&rexec->action_env,
+ &aexec->tr_context);
+ aexec->status = status;
+ sieve_action_execution_post(rexec);
+ }
+ return status;
+}
+
+static int
+sieve_result_action_execute(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ int start_status)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+ struct sieve_side_effect_execution *seexec;
+ int status = start_status;
+ bool impl_keep = TRUE;
+
+ /* Skip actions that are already executed. */
+ if (aexec->state >= SIEVE_ACTION_EXECUTION_STATE_EXECUTED)
+ return status;
+ aexec->state = SIEVE_ACTION_EXECUTION_STATE_EXECUTED;
+
+ /* Record explicit keep when it is not the final implicit keep */
+ if (act->keep && aexec != &rexec->keep)
+ rexec->keep_explicit = TRUE;
+
+ /* Skip non-actions (inactive keep) */
+ if (act->def == NULL) {
+ i_assert(aexec != &rexec->keep);
+ if (act->keep)
+ e_debug(rexec->event, "Executed explicit keep");
+ return status;
+ }
+
+ /* Don't execute if others already failed */
+ if (status != SIEVE_EXEC_OK)
+ return status;
+
+ if (aexec == &rexec->keep)
+ e_debug(rexec->event, "Executing implicit keep action");
+ else {
+ e_debug(rexec->event, "Executing %s action%s",
+ sieve_action_name(act),
+ (act->keep ? " (explicit keep)" : ""));
+ }
+
+ sieve_action_execution_pre(rexec, aexec);
+
+ /* Execute pre-execute event of side effects */
+ seexec = aexec->seffects_head;
+ while (status == SIEVE_EXEC_OK && seexec != NULL) {
+ status = sieve_result_side_effect_pre_execute(
+ rexec, aexec, seexec);
+ seexec = seexec->next;
+ }
+
+ /* Execute the action itself */
+ if (status == SIEVE_EXEC_OK && act->def != NULL &&
+ act->def->execute != NULL) {
+ status = act->def->execute(&rexec->action_env,
+ aexec->tr_context,
+ &impl_keep);
+ if (status == SIEVE_EXEC_OK)
+ rexec->executed = TRUE;
+ }
+
+ /* Execute post-execute event of side effects */
+ seexec = aexec->seffects_head;
+ while (status == SIEVE_EXEC_OK && seexec != NULL) {
+ status = sieve_result_side_effect_post_execute(
+ rexec, aexec, seexec, &impl_keep);
+ seexec = seexec->next;
+ }
+
+ if (aexec == &rexec->keep) {
+ e_debug(rexec->event,
+ "Finished executing implicit keep action (status=%s)",
+ sieve_execution_exitcode_to_str(status));
+ } else {
+ e_debug(rexec->event, "Finished executing %s action "
+ "(status=%s, keep=%s)", sieve_action_name(act),
+ sieve_execution_exitcode_to_str(status),
+ (act->keep ? "explicit" :
+ (impl_keep && rexec->keep_implicit ?
+ "implicit" : "canceled")));
+ }
+
+ if (status == SIEVE_EXEC_OK &&
+ (act->def->flags & SIEVE_ACTFLAG_TRIES_DELIVER) != 0)
+ rexec->seen_delivery = TRUE;
+
+ /* Update implicit keep status (but only when we're not running the
+ implicit keep right now). */
+ if (aexec != &rexec->keep)
+ rexec->keep_implicit = rexec->keep_implicit && impl_keep;
+
+ sieve_action_execution_post(rexec);
+
+ aexec->status = status;
+ return status;
+}
+
+static int
+sieve_result_action_commit(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+ struct sieve_side_effect_execution *seexec;
+ int cstatus = SIEVE_EXEC_OK;
+
+ if (aexec == &rexec->keep) {
+ e_debug(rexec->event, "Commit implicit keep action");
+ } else {
+ e_debug(rexec->event, "Commit %s action%s",
+ sieve_action_name(act),
+ (act->keep ? " (explicit keep)" : ""));
+ }
+
+ sieve_action_execution_pre(rexec, aexec);
+
+ if (act->def->commit != NULL) {
+ cstatus = act->def->commit(&rexec->action_env,
+ aexec->tr_context);
+ if (cstatus == SIEVE_EXEC_OK)
+ rexec->committed = TRUE;
+ }
+
+ /* Execute post_commit event of side effects */
+ seexec = aexec->seffects_head;
+ while (seexec != NULL) {
+ sieve_result_side_effect_post_commit(
+ rexec, aexec, seexec, cstatus);
+ seexec = seexec->next;
+ }
+
+ sieve_action_execution_post(rexec);
+
+ return cstatus;
+}
+
+static void
+sieve_result_action_rollback(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+ struct sieve_side_effect_execution *seexec;
+
+ if (aexec == &rexec->keep) {
+ e_debug(rexec->event, "Roll back implicit keep action");
+ } else {
+ e_debug(rexec->event, "Roll back %s action%s",
+ sieve_action_name(act),
+ (act->keep ? " (explicit keep)" : ""));
+ }
+
+ sieve_action_execution_pre(rexec, aexec);
+
+ if (act->def->rollback != NULL) {
+ act->def->rollback(&rexec->action_env, aexec->tr_context,
+ (aexec->status == SIEVE_EXEC_OK));
+ }
+
+ /* Rollback side effects */
+ seexec = aexec->seffects_head;
+ while (seexec != NULL) {
+ sieve_result_side_effect_rollback(rexec, aexec, seexec);
+ seexec = seexec->next;
+ }
+
+ sieve_action_execution_post(rexec);
+}
+
+static int
+sieve_result_action_commit_or_rollback(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec,
+ int status, int *commit_status)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+
+ /* Skip actions that are already finalized. */
+ if (aexec->state >= SIEVE_ACTION_EXECUTION_STATE_FINALIZED)
+ return status;
+ aexec->state = SIEVE_ACTION_EXECUTION_STATE_FINALIZED;
+
+ if (aexec == &rexec->keep) {
+ e_debug(rexec->event, "Finalize implicit keep action"
+ "(status=%s, action_status=%s, commit_status=%s)",
+ sieve_execution_exitcode_to_str(status),
+ sieve_execution_exitcode_to_str(aexec->status),
+ sieve_execution_exitcode_to_str(*commit_status));
+ } else {
+ e_debug(rexec->event, "Finalize %s action "
+ "(%sstatus=%s, action_status=%s, commit_status=%s, "
+ "pre-commit=%s)",
+ sieve_action_name(act),
+ (act->keep ? "explicit keep, " : ""),
+ sieve_execution_exitcode_to_str(status),
+ sieve_execution_exitcode_to_str(aexec->status),
+ sieve_execution_exitcode_to_str(*commit_status),
+ (aexec->commit ? "yes" : "no"));
+ }
+
+ /* Skip non-actions (inactive keep) */
+ if (act->def == NULL)
+ return status;
+
+ if (aexec->status == SIEVE_EXEC_OK &&
+ (status == SIEVE_EXEC_OK ||
+ (aexec->commit && *commit_status == SIEVE_EXEC_OK))) {
+ int cstatus = SIEVE_EXEC_OK;
+
+ cstatus = sieve_result_action_commit(rexec, aexec);
+ if (cstatus != SIEVE_EXEC_OK) {
+ /* This is bad; try to salvage as much as possible */
+ if (*commit_status == SIEVE_EXEC_OK) {
+ *commit_status = cstatus;
+ if (!rexec->committed) {
+ /* We haven't executed anything yet;
+ continue as rollback */
+ status = cstatus;
+ }
+ }
+ }
+ } else {
+ sieve_result_action_rollback(rexec, aexec);
+ }
+
+ if (act->keep) {
+ if (status == SIEVE_EXEC_FAILURE)
+ status = SIEVE_EXEC_KEEP_FAILED;
+ if (*commit_status == SIEVE_EXEC_FAILURE)
+ *commit_status = SIEVE_EXEC_KEEP_FAILED;
+ }
+
+ return status;
+}
+
+static void
+sieve_result_action_finish(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec, int status)
+{
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+
+ /* Skip non-actions (inactive keep) */
+ if (act->def == NULL)
+ return;
+
+ if (aexec == &rexec->keep) {
+ e_debug(rexec->event, "Finish implicit keep action");
+ } else {
+ e_debug(rexec->event, "Finish %s action%s",
+ sieve_action_name(act),
+ (act->keep ? " (explicit keep)" : ""));
+ }
+
+ if (act->def->finish != NULL) {
+ sieve_action_execution_pre(rexec, aexec);
+ act->def->finish(&rexec->action_env, aexec->tr_context, status);
+ sieve_action_execution_post(rexec);
+ }
+}
+
+static void
+sieve_result_action_abort(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ if (aexec->state > SIEVE_ACTION_EXECUTION_STATE_INIT &&
+ aexec->state < SIEVE_ACTION_EXECUTION_STATE_FINALIZED)
+ sieve_result_action_rollback(rexec, aexec);
+ DLLIST2_REMOVE(&rexec->actions_head, &rexec->actions_tail, aexec);
+}
+
+static void
+sieve_action_execution_update(struct sieve_result_execution *rexec,
+ struct sieve_action_execution *aexec)
+{
+ const struct sieve_action_exec_env *aenv = &rexec->action_env;
+ struct sieve_result *result = aenv->result;
+ struct sieve_result_action *rac;
+
+ rac = result->actions_head;
+ while (rac != NULL) {
+ if (aexec->action == rac)
+ break;
+ rac = rac->next;
+ }
+
+ if (rac == NULL) {
+ /* Action was removed; abort it. */
+ sieve_result_action_abort(rexec, aexec);
+ return;
+ }
+
+ if (aexec->exec_seq != rac->action.exec_seq) {
+ i_assert(rac->action.keep);
+
+ /* Recycled keep */
+ aexec->exec_seq = rac->action.exec_seq;
+ aexec->state = SIEVE_ACTION_EXECUTION_STATE_INIT;
+ }
+
+ sieve_action_execution_add_side_effects(rexec, aexec, rac);
+}
+
+static void
+sieve_result_execution_add_action(struct sieve_result_execution *rexec,
+ struct sieve_result_action *rac)
+{
+ struct sieve_action_execution *aexec;
+
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ if (aexec->action == rac)
+ return;
+ aexec = aexec->next;
+ }
+
+ aexec = p_new(rexec->pool, struct sieve_action_execution, 1);
+ aexec->action = rac;
+ aexec->exec_seq = rac->action.exec_seq;
+ aexec->ehandler = rexec->ehandler;
+
+ DLLIST2_APPEND(&rexec->actions_head, &rexec->actions_tail, aexec);
+
+ sieve_action_execution_add_side_effects(rexec, aexec, rac);
+}
+
+/* Result */
+
+struct sieve_result_execution *
+sieve_result_execution_create(struct sieve_result *result, pool_t pool)
+{
+ struct sieve_result_execution *rexec;
+
+ pool_ref(pool);
+ rexec = p_new(pool, struct sieve_result_execution, 1);
+ rexec->pool = pool;
+ rexec->event = result->event;
+ rexec->action_env.result = result;
+ rexec->action_env.event = result->event;
+ rexec->action_env.exec_env = result->exec_env;
+ rexec->action_env.msgctx = result->msgctx;
+ rexec->status = SIEVE_EXEC_OK;
+ rexec->keep_success = TRUE;
+ rexec->keep_status = SIEVE_EXEC_OK;
+ rexec->keep_explicit = FALSE;
+ rexec->keep_implicit = TRUE;
+
+ sieve_result_ref(result);
+ result->exec = rexec;
+
+ return rexec;
+}
+
+void sieve_result_execution_destroy(struct sieve_result_execution **_rexec)
+{
+ struct sieve_result_execution *rexec = *_rexec;
+
+ *_rexec = NULL;
+
+ if (rexec == NULL)
+ return;
+
+ rexec->action_env.result->exec = NULL;
+ sieve_result_unref(&rexec->action_env.result);
+ pool_unref(&rexec->pool);
+}
+
+static void
+sieve_result_implicit_keep_execute(struct sieve_result_execution *rexec)
+{
+ const struct sieve_action_exec_env *aenv = &rexec->action_env;
+ struct sieve_result *result = aenv->result;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_action_execution *aexec;
+ int status = SIEVE_EXEC_OK;
+ struct sieve_action_execution *aexec_keep = &rexec->keep;
+ struct sieve_result_action *ract_keep = &rexec->keep_action;
+ struct sieve_action *act_keep = &ract_keep->action;
+ bool success = FALSE;
+
+ switch (rexec->status) {
+ case SIEVE_EXEC_OK:
+ success = TRUE;
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ if (rexec->committed) {
+ e_debug(rexec->event,
+ "Temporary failure occurred (status=%s), "
+ "but other actions were already committed: "
+ "execute failure implicit keep",
+ sieve_execution_exitcode_to_str(rexec->status));
+ break;
+ }
+ if (rexec->keep_finalizing)
+ break;
+
+ e_debug(rexec->event,
+ "Skip implicit keep for temporary failure "
+ "(state=execute, status=%s)",
+ sieve_execution_exitcode_to_str(rexec->status));
+ return;
+ default:
+ break;
+ }
+
+ if (rexec->keep_equiv_action != NULL) {
+ e_debug(rexec->event, "No implicit keep needed "
+ "(equivalent action already executed)");
+ return;
+ }
+
+ rexec->keep.action = &rexec->keep_action;
+ rexec->keep.ehandler = rexec->ehandler;
+ rexec->keep_success = success;
+ rexec->keep_status = status;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_DEFER_KEEP) != 0) {
+ e_debug(rexec->event, "Execution of implicit keep is deferred");
+ return;
+ }
+
+ if (!success)
+ *act_keep = result->failure_action;
+ else
+ *act_keep = result->keep_action;
+ act_keep->name = "keep";
+ act_keep->mail = NULL;
+ act_keep->keep = TRUE;
+
+ /* If keep is a non-action, return right away */
+ if (act_keep->def == NULL) {
+ e_debug(rexec->event, "Keep is not defined yet");
+ return;
+ }
+
+ /* Scan for execution of keep-equal actions */
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ struct sieve_result_action *rac = aexec->action;
+
+ if (rac->action.def == act_keep->def &&
+ act_keep->def->equals != NULL &&
+ act_keep->def->equals(eenv->scriptenv, NULL,
+ &rac->action) &&
+ aexec->state >= SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+ e_debug(rexec->event, "No implicit keep needed "
+ "(equivalent %s action already executed)",
+ sieve_action_name(&rac->action));
+ rexec->keep_equiv_action = aexec;
+ return;
+ }
+
+ aexec = aexec->next;
+ }
+
+ /* Scan for deferred keep */
+ aexec = rexec->actions_tail;
+ while (aexec != NULL) {
+ struct sieve_result_action *rac = aexec->action;
+
+ if (aexec->state < SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+ aexec = NULL;
+ break;
+ }
+ if (rac->action.keep && rac->action.def == NULL)
+ break;
+ aexec = aexec->prev;
+ }
+
+ if (aexec == NULL) {
+ if (success)
+ act_keep->mail = sieve_message_get_mail(aenv->msgctx);
+ } else {
+ e_debug(rexec->event, "Found deferred keep action");
+
+ if (success) {
+ act_keep->location = aexec->action->action.location;
+ act_keep->mail = aexec->action->action.mail;
+ ract_keep->seffects = aexec->action->seffects;
+ }
+ aexec->state = SIEVE_ACTION_EXECUTION_STATE_FINALIZED;
+ }
+
+ if (ract_keep->seffects == NULL) {
+ /* Apply any implicit side effects if applicable */
+ if (success && hash_table_is_created(result->action_contexts)) {
+ struct sieve_result_action_context *actctx;
+
+ /* Check for implicit side effects to keep action */
+ actctx = hash_table_lookup(result->action_contexts,
+ act_keep->def);
+
+ if (actctx != NULL)
+ ract_keep->seffects = actctx->seffects;
+ }
+ }
+
+ e_debug(rexec->event, "Execute implicit keep (status=%s)",
+ sieve_execution_exitcode_to_str(rexec->status));
+
+ /* Initialize side effects */
+ sieve_action_execution_add_side_effects(rexec, aexec_keep, ract_keep);
+
+ /* Initialize keep action event */
+ sieve_result_init_action_event(result, act_keep, FALSE);
+
+ /* Start keep action */
+ status = sieve_result_action_start(rexec, aexec_keep);
+
+ /* Execute keep action */
+ if (status == SIEVE_EXEC_OK)
+ status = sieve_result_action_execute(rexec, aexec_keep, status);
+ if (status == SIEVE_EXEC_OK)
+ aexec_keep->commit = TRUE;
+
+ rexec->executed_delivery = rexec->seen_delivery;
+ rexec->keep_status = status;
+ sieve_action_execution_post(rexec);
+}
+
+static int
+sieve_result_implicit_keep_finalize(struct sieve_result_execution *rexec)
+{
+ const struct sieve_action_exec_env *aenv = &rexec->action_env;
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_action_execution *aexec_keep = &rexec->keep;
+ struct sieve_result_action *ract_keep = &rexec->keep_action;
+ struct sieve_action *act_keep = &ract_keep->action;
+ int commit_status = SIEVE_EXEC_OK;
+ bool success = FALSE, temp_failure = FALSE;
+
+ switch (rexec->status) {
+ case SIEVE_EXEC_OK:
+ success = TRUE;
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ if (rexec->committed) {
+ e_debug(rexec->event,
+ "Temporary failure occurred (status=%s), "
+ "but other actions were already committed: "
+ "commit failure implicit keep",
+ sieve_execution_exitcode_to_str(rexec->status));
+ break;
+ }
+
+ if (aexec_keep->state !=
+ SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+ e_debug(rexec->event,
+ "Skip implicit keep for temporary failure "
+ "(state=commit, status=%s)",
+ sieve_execution_exitcode_to_str(rexec->status));
+ return rexec->status;
+ }
+ /* Roll back for temporary failure when no other action
+ is committed. */
+ commit_status = rexec->status;
+ temp_failure = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_DEFER_KEEP) != 0) {
+ e_debug(rexec->event, "Execution of implicit keep is deferred");
+ return rexec->keep_status;
+ }
+
+ rexec->keep_finalizing = TRUE;
+
+ /* Start keep if necessary */
+ if (temp_failure) {
+ rexec->keep_status = rexec->status;
+ } else if (act_keep->def == NULL ||
+ aexec_keep->state != SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+ sieve_result_implicit_keep_execute(rexec);
+ /* Switch to failure keep if necessary. */
+ } else if (rexec->keep_success && !success){
+ e_debug(rexec->event, "Switch to failure implicit keep");
+
+ /* Failed transaction, rollback success keep action. */
+ sieve_result_action_rollback(rexec, aexec_keep);
+
+ event_unref(&act_keep->event);
+ i_zero(aexec_keep);
+
+ /* Start failure keep action. */
+ sieve_result_implicit_keep_execute(rexec);
+ }
+ if (act_keep->def == NULL)
+ return rexec->keep_status;
+
+ if (rexec->keep_equiv_action != NULL) {
+ struct sieve_action_execution *ke_aexec =
+ rexec->keep_equiv_action;
+
+ i_assert(ke_aexec->state >=
+ SIEVE_ACTION_EXECUTION_STATE_FINALIZED);
+
+ e_debug(rexec->event, "No implicit keep needed "
+ "(equivalent %s action already finalized)",
+ sieve_action_name(&ke_aexec->action->action));
+ return ke_aexec->status;
+ }
+
+ e_debug(rexec->event, "Finalize implicit keep (status=%s)",
+ sieve_execution_exitcode_to_str(rexec->status));
+
+ i_assert(aexec_keep->state == SIEVE_ACTION_EXECUTION_STATE_EXECUTED);
+
+ /* Finalize keep action */
+ rexec->keep_status = sieve_result_action_commit_or_rollback(
+ rexec, aexec_keep, rexec->keep_status, &commit_status);
+
+ /* Finish keep action */
+ sieve_result_action_finish(rexec, aexec_keep,
+ rexec->keep_status);
+
+ sieve_action_execution_post(rexec);
+ event_unref(&act_keep->event);
+
+ if (rexec->keep_status == SIEVE_EXEC_FAILURE)
+ rexec->keep_status = SIEVE_EXEC_KEEP_FAILED;
+ return rexec->keep_status;
+}
+
+bool sieve_result_executed(struct sieve_result_execution *rexec)
+{
+ return rexec->executed;
+}
+
+bool sieve_result_committed(struct sieve_result_execution *rexec)
+{
+ return rexec->committed;
+}
+
+bool sieve_result_executed_delivery(struct sieve_result_execution *rexec)
+{
+ return rexec->executed_delivery;
+}
+
+static int sieve_result_transaction_start(struct sieve_result_execution *rexec)
+{
+ struct sieve_action_execution *aexec;
+ int status = SIEVE_EXEC_OK;
+
+ e_debug(rexec->event, "Starting execution of actions");
+
+ aexec = rexec->actions_head;
+ while (status == SIEVE_EXEC_OK && aexec != NULL) {
+ status = sieve_result_action_start(rexec, aexec);
+ aexec = aexec->next;
+ }
+ sieve_action_execution_post(rexec);
+
+ return status;
+}
+
+static int
+sieve_result_transaction_execute(struct sieve_result_execution *rexec,
+ int start_status)
+{
+ struct sieve_action_execution *aexec;
+ int status = SIEVE_EXEC_OK;
+
+ e_debug(rexec->event, "Executing actions");
+
+ rexec->seen_delivery = FALSE;
+ aexec = rexec->actions_head;
+ while (status == SIEVE_EXEC_OK && aexec != NULL) {
+ status = sieve_result_action_execute(rexec, aexec,
+ start_status);
+ aexec = aexec->next;
+ }
+ sieve_action_execution_post(rexec);
+
+ if (status == SIEVE_EXEC_OK) {
+ /* Since this execution series is successful so far, mark all
+ actions in it to be committed. */
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ aexec->commit = TRUE;
+ aexec = aexec->next;
+ }
+
+ rexec->executed_delivery =
+ rexec->executed_delivery || rexec->seen_delivery;
+ }
+
+ e_debug(rexec->event, "Finished executing actions "
+ "(status=%s, keep=%s, executed=%s)",
+ sieve_execution_exitcode_to_str(status),
+ (rexec->keep_explicit ? "explicit" :
+ (rexec->keep_implicit ? "implicit" : "none")),
+ (rexec->executed ? "yes" : "no"));
+ return status;
+}
+
+static int
+sieve_result_transaction_commit_or_rollback(
+ struct sieve_result_execution *rexec, int status)
+{
+ struct sieve_action_execution *aexec;
+ int commit_status = SIEVE_EXEC_OK;
+
+ switch (status) {
+ case SIEVE_EXEC_TEMP_FAILURE:
+ /* Roll back all actions */
+ commit_status = status;
+ break;
+ default:
+ break;
+ }
+
+ e_debug(rexec->event, "Finalizing actions");
+
+ /* First commit/rollback all storage actions */
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+
+ if (act->def == NULL ||
+ (act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) == 0) {
+ aexec = aexec->next;
+ continue;
+ }
+
+ status = sieve_result_action_commit_or_rollback(
+ rexec, aexec, status, &commit_status);
+
+ aexec = aexec->next;
+ }
+
+ /* Then commit/rollback all other actions */
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ struct sieve_result_action *rac = aexec->action;
+ struct sieve_action *act = &rac->action;
+
+ if (act->def != NULL &&
+ (act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) != 0) {
+ aexec = aexec->next;
+ continue;
+ }
+
+ status = sieve_result_action_commit_or_rollback(
+ rexec, aexec, status, &commit_status);
+
+ aexec = aexec->next;
+ }
+
+ e_debug(rexec->event, "Finished finalizing actions "
+ "(status=%s, keep=%s, committed=%s)",
+ sieve_execution_exitcode_to_str(status),
+ (rexec->keep_explicit ? "explicit" :
+ (rexec->keep_implicit ? "implicit" : "none")),
+ (rexec->committed ? "yes" : "no"));
+
+ return commit_status;
+}
+
+static void
+sieve_result_transaction_finish(struct sieve_result_execution *rexec,
+ int status)
+{
+ struct sieve_action_execution *aexec;
+
+ e_debug(rexec->event, "Finishing actions");
+
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ sieve_result_action_finish(rexec, aexec, status);
+ aexec = aexec->next;
+ }
+ sieve_action_execution_post(rexec);
+}
+
+static void
+sieve_result_execute_update_status(struct sieve_result_execution *rexec,
+ int status)
+{
+ switch (status) {
+ case SIEVE_EXEC_OK:
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ rexec->status = status;
+ break;
+ case SIEVE_EXEC_BIN_CORRUPT:
+ i_unreached();
+ case SIEVE_EXEC_FAILURE:
+ case SIEVE_EXEC_KEEP_FAILED:
+ if (rexec->status == SIEVE_EXEC_OK)
+ rexec->status = status;
+ break;
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ if (rexec->status != SIEVE_EXEC_TEMP_FAILURE)
+ rexec->status = status;
+ break;
+ }
+}
+
+static void
+sieve_result_execution_update(struct sieve_result_execution *rexec)
+{
+ const struct sieve_action_exec_env *aenv = &rexec->action_env;
+ struct sieve_result *result = aenv->result;
+ struct sieve_action_execution *aexec;
+ struct sieve_result_action *rac;
+
+ aexec = rexec->actions_head;
+ while (aexec != NULL) {
+ struct sieve_action_execution *aexec_next = aexec->next;
+
+ sieve_action_execution_update(rexec, aexec);
+ aexec = aexec_next;
+ }
+
+ rac = result->actions_head;
+ while (rac != NULL) {
+ sieve_result_execution_add_action(rexec, rac);
+ rac = rac->next;
+ }
+}
+
+int sieve_result_execute(struct sieve_result_execution *rexec, int status,
+ bool commit, struct sieve_error_handler *ehandler,
+ bool *keep_r)
+{
+ const struct sieve_action_exec_env *aenv = &rexec->action_env;
+ struct sieve_result *result = aenv->result;
+ int result_status, ret;
+
+ e_debug(rexec->event, "Executing result (status=%s, commit=%s)",
+ sieve_execution_exitcode_to_str(status),
+ (commit ? "yes" : "no"));
+
+ if (keep_r != NULL)
+ *keep_r = FALSE;
+ sieve_result_mark_executed(result);
+
+ /* Prepare environment */
+
+ rexec->ehandler = ehandler;
+
+ /* Update actions in execution from result */
+
+ sieve_result_execution_update(rexec);
+
+ /* Transaction start and execute */
+
+ if (status != SIEVE_EXEC_OK) {
+ sieve_result_execute_update_status(rexec, status);
+ } else if (rexec->status == SIEVE_EXEC_OK) {
+ /* Transaction start */
+
+ status = sieve_result_transaction_start(rexec);
+
+ /* Transaction execute */
+
+ status = sieve_result_transaction_execute(rexec, status);
+ sieve_result_execute_update_status(rexec, status);
+ }
+
+ if (!commit) {
+ sieve_action_execution_post(rexec);
+ rexec->ehandler = NULL;
+
+ /* Merge explicit keep status into implicit keep for the next
+ execution round. */
+ rexec->keep_implicit = (rexec->keep_explicit ||
+ rexec->keep_implicit);
+ rexec->keep_explicit = FALSE;
+
+ e_debug(rexec->event, "Finished executing result "
+ "(no commit, status=%s, keep=%s)",
+ sieve_execution_exitcode_to_str(rexec->status),
+ (rexec->keep_implicit ? "yes" : "no"));
+
+ if (keep_r != NULL)
+ *keep_r = rexec->keep_implicit;
+ return rexec->status;
+ }
+
+ /* Execute implicit keep if the transaction failed or when the
+ implicit keep was not canceled during transaction.
+ */
+ if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit)
+ sieve_result_implicit_keep_execute(rexec);
+
+ /* Transaction commit/rollback */
+
+ status = sieve_result_transaction_commit_or_rollback(rexec, status);
+ sieve_result_execute_update_status(rexec, status);
+
+ /* Commit implicit keep if necessary */
+
+ result_status = rexec->status;
+
+ /* Commit implicit keep if the transaction failed or when the
+ implicit keep was not canceled during transaction.
+ */
+ if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit) {
+ ret = sieve_result_implicit_keep_finalize(rexec);
+ switch (ret) {
+ case SIEVE_EXEC_OK:
+ if (result_status == SIEVE_EXEC_TEMP_FAILURE)
+ result_status = SIEVE_EXEC_FAILURE;
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ if (!rexec->committed) {
+ result_status = ret;
+ break;
+ }
+ /* fall through */
+ default:
+ result_status = SIEVE_EXEC_KEEP_FAILED;
+ }
+ }
+ if (rexec->status == SIEVE_EXEC_OK)
+ rexec->status = result_status;
+
+ /* Finish execution */
+
+ sieve_result_transaction_finish(rexec, rexec->status);
+
+ sieve_action_execution_post(rexec);
+ rexec->ehandler = NULL;
+
+ rexec->status = result_status;
+
+ /* Merge explicit keep status into implicit keep (in this case only for
+ completeness).
+ */
+ rexec->keep_implicit = (rexec->keep_explicit ||
+ rexec->keep_implicit);
+ rexec->keep_explicit = FALSE;
+
+ e_debug(rexec->event, "Finished executing result "
+ "(final, status=%s, keep=%s)",
+ sieve_execution_exitcode_to_str(result_status),
+ (rexec->keep_implicit ? "yes" : "no"));
+
+ if (keep_r != NULL)
+ *keep_r = rexec->keep_implicit;
+ return result_status;
+}
+
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context {
+ struct sieve_result *result;
+ struct sieve_result_action *current_action;
+ struct sieve_result_action *next_action;
+};
+
+struct sieve_result_iterate_context *
+sieve_result_iterate_init(struct sieve_result *result)
+{
+ struct sieve_result_iterate_context *rictx =
+ t_new(struct sieve_result_iterate_context, 1);
+
+ rictx->result = result;
+ rictx->current_action = NULL;
+ rictx->next_action = result->actions_head;
+
+ return rictx;
+}
+
+const struct sieve_action *
+sieve_result_iterate_next(struct sieve_result_iterate_context *rictx,
+ bool *keep)
+{
+ struct sieve_result_action *rac;
+
+ if (rictx == NULL)
+ return NULL;
+
+ rac = rictx->current_action = rictx->next_action;
+ if (rac != NULL) {
+ rictx->next_action = rac->next;
+
+ if (keep != NULL)
+ *keep = rac->action.keep;
+
+ return &rac->action;
+ }
+
+ return NULL;
+}
+
+void sieve_result_iterate_delete(struct sieve_result_iterate_context *rictx)
+{
+ struct sieve_result *result;
+ struct sieve_result_action *rac;
+
+ if (rictx == NULL || rictx->current_action == NULL)
+ return;
+
+ result = rictx->result;
+ rac = rictx->current_action;
+
+ /* Delete action */
+
+ if (rac->prev == NULL)
+ result->actions_head = rac->next;
+ else
+ rac->prev->next = rac->next;
+
+ if (rac->next == NULL)
+ result->actions_tail = rac->prev;
+ else
+ rac->next->prev = rac->prev;
+
+ sieve_result_action_deinit(rac);
+
+ /* Skip to next action in iteration */
+
+ rictx->current_action = NULL;
+}
+
+/*
+ * Side effects list
+ */
+
+struct sieve_side_effects_list *
+sieve_side_effects_list_create(struct sieve_result *result)
+{
+ struct sieve_side_effects_list *list =
+ p_new(result->pool, struct sieve_side_effects_list, 1);
+
+ list->result = result;
+ list->first_effect = NULL;
+ list->last_effect = NULL;
+
+ return list;
+}
+
+void sieve_side_effects_list_add(struct sieve_side_effects_list *list,
+ const struct sieve_side_effect *seffect)
+{
+ struct sieve_result_side_effect *reffect, *reffect_pos;
+
+ /* Prevent duplicates */
+ reffect = list->first_effect;
+ reffect_pos = NULL;
+ while (reffect != NULL) {
+ const struct sieve_side_effect_def *ref_def = reffect->seffect.def;
+ const struct sieve_side_effect_def *sef_def = seffect->def;
+
+ i_assert(ref_def != NULL);
+ i_assert(sef_def != NULL);
+
+ if (sef_def == ref_def) {
+ /* already listed */
+ i_assert(reffect_pos == NULL);
+ return;
+ }
+ if (sef_def->precedence > ref_def->precedence) {
+ /* insert it before this position */
+ reffect_pos = reffect;
+ }
+
+ reffect = reffect->next;
+ }
+
+ /* Create new side effect object */
+ reffect = p_new(list->result->pool, struct sieve_result_side_effect, 1);
+ reffect->seffect = *seffect;
+
+ if (reffect_pos != NULL) {
+ /* Insert */
+ reffect->next = reffect_pos;
+ reffect_pos->prev = reffect;
+ if (list->first_effect == reffect_pos)
+ list->first_effect = reffect;
+ } else {
+ /* Add */
+ if ( list->first_effect == NULL ) {
+ list->first_effect = reffect;
+ list->last_effect = reffect;
+ reffect->prev = NULL;
+ reffect->next = NULL;
+ } else {
+ list->last_effect->next = reffect;
+ reffect->prev = list->last_effect;
+ list->last_effect = reffect;
+ reffect->next = NULL;
+ }
+ }
+}
+
+/*
+ * Error handling
+ */
+
+#undef sieve_result_error
+void sieve_result_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_logv(aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_global_error
+void sieve_result_global_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_logv(eenv->svinst, aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_warning
+void sieve_result_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_logv(aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_global_warning
+void sieve_result_global_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_logv(eenv->svinst, aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_log
+void sieve_result_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = (HAS_ALL_BITS(eenv->flags,
+ SIEVE_EXECUTE_FLAG_LOG_RESULT) ?
+ LOG_TYPE_INFO : LOG_TYPE_DEBUG),
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_logv(aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_global_log
+void sieve_result_global_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = (HAS_ALL_BITS(eenv->flags,
+ SIEVE_EXECUTE_FLAG_LOG_RESULT) ?
+ LOG_TYPE_INFO : LOG_TYPE_DEBUG),
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_logv(eenv->svinst, aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_global_log_error
+void sieve_result_global_log_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_info_logv(eenv->svinst, aenv->ehandler, &params,
+ fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_global_log_warning
+void sieve_result_global_log_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_info_logv(eenv->svinst, aenv->ehandler, &params,
+ fmt, args);
+ va_end(args);
+}
+
+#undef sieve_result_event_log
+void sieve_result_event_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, struct event *event,
+ const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = (HAS_ALL_BITS(eenv->flags,
+ SIEVE_EXECUTE_FLAG_LOG_RESULT) ?
+ LOG_TYPE_INFO : LOG_TYPE_DEBUG),
+ .event = event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ sieve_global_logv(eenv->svinst, aenv->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+
+#undef sieve_result_critical
+void sieve_result_critical(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *user_prefix, const char *fmt, ...)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .event = aenv->event,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ va_start(args, fmt);
+
+ T_BEGIN {
+ sieve_criticalv(eenv->svinst, aenv->ehandler, &params,
+ user_prefix, fmt, args);
+ } T_END;
+
+ va_end(args);
+}
+
+#undef sieve_result_mail_error
+int sieve_result_mail_error(const struct sieve_action_exec_env *aenv,
+ struct mail *mail,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+{
+ const char *error_msg, *user_prefix;
+ va_list args;
+
+ error_msg = mailbox_get_last_internal_error(mail->box, NULL);
+
+ va_start(args, fmt);
+ user_prefix = t_strdup_vprintf(fmt, args);
+ sieve_result_critical(aenv, csrc_filename, csrc_linenum,
+ user_prefix, "%s: %s", user_prefix, error_msg);
+ va_end(args);
+
+ return SIEVE_EXEC_TEMP_FAILURE;
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-result.h b/pigeonhole/src/lib-sieve/sieve-result.h
new file mode 100644
index 0000000..a9fcadc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-result.h
@@ -0,0 +1,224 @@
+#ifndef SIEVE_RESULT_H
+#define SIEVE_RESULT_H
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-execute.h"
+
+/*
+ * Types
+ */
+
+struct sieve_side_effects_list;
+
+/*
+ * Result object
+ */
+
+struct sieve_result;
+
+struct sieve_result *
+sieve_result_create(struct sieve_instance *svinst, pool_t pool,
+ const struct sieve_execute_env *eenv);
+
+void sieve_result_ref(struct sieve_result *result);
+
+void sieve_result_unref(struct sieve_result **result);
+
+pool_t sieve_result_pool(struct sieve_result *result);
+
+/*
+ * Getters/Setters
+ */
+
+const struct sieve_script_env *
+sieve_result_get_script_env(struct sieve_result *result);
+const struct sieve_message_data *
+sieve_result_get_message_data(struct sieve_result *result);
+struct sieve_message_context *
+sieve_result_get_message_context(struct sieve_result *result);
+unsigned int sieve_result_get_exec_seq(struct sieve_result *result);
+
+/*
+ * Extension support
+ */
+
+void sieve_result_extension_set_context(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ void *context);
+const void *
+sieve_result_extension_get_context(struct sieve_result *result,
+ const struct sieve_extension *ext);
+
+/*
+ * Result printing
+ */
+
+struct sieve_result_print_env {
+ struct sieve_result *result;
+ const struct sieve_script_env *scriptenv;
+ struct ostream *stream;
+};
+
+void sieve_result_vprintf(const struct sieve_result_print_env *penv,
+ const char *fmt, va_list args) ATTR_FORMAT(2, 0);
+void sieve_result_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_result_action_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+void sieve_result_seffect_printf(const struct sieve_result_print_env *penv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+bool sieve_result_print(struct sieve_result *result,
+ const struct sieve_script_env *senv,
+ struct ostream *stream, bool *keep);
+
+/*
+ * Result composition
+ */
+
+void sieve_result_add_implicit_side_effect(
+ struct sieve_result *result, const struct sieve_action_def *to_action,
+ bool to_keep, const struct sieve_extension *ext,
+ const struct sieve_side_effect_def *seffect, void *context);
+
+int sieve_result_add_action(const struct sieve_runtime_env *renv,
+ const struct sieve_extension *ext, const char *name,
+ const struct sieve_action_def *act_def,
+ struct sieve_side_effects_list *seffects,
+ void *context, unsigned int instance_limit,
+ bool preserve_mail);
+int sieve_result_add_keep(const struct sieve_runtime_env *renv,
+ struct sieve_side_effects_list *seffects);
+
+void sieve_result_set_keep_action(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ const struct sieve_action_def *act_def);
+void sieve_result_set_failure_action(struct sieve_result *result,
+ const struct sieve_extension *ext,
+ const struct sieve_action_def *act_def);
+
+/*
+ * Result execution
+ */
+
+struct sieve_result_execution;
+
+void sieve_result_mark_executed(struct sieve_result *result);
+
+struct sieve_result_execution *
+sieve_result_execution_create(struct sieve_result *result, pool_t pool);
+void sieve_result_execution_destroy(struct sieve_result_execution **_rexec);
+
+void *sieve_result_execution_get_dup_transaction(
+ struct sieve_result_execution *rexec);
+
+int sieve_result_execute(struct sieve_result_execution *rexec, int status,
+ bool commit, struct sieve_error_handler *ehandler,
+ bool *keep_r);
+
+bool sieve_result_executed(struct sieve_result_execution *rexec);
+bool sieve_result_committed(struct sieve_result_execution *rexec);
+
+bool sieve_result_executed_delivery(struct sieve_result_execution *rexec);
+
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context;
+
+struct sieve_result_iterate_context *
+sieve_result_iterate_init(struct sieve_result *result);
+const struct sieve_action *
+sieve_result_iterate_next(struct sieve_result_iterate_context *rictx,
+ bool *keep);
+void sieve_result_iterate_delete(struct sieve_result_iterate_context *rictx);
+
+/*
+ * Side effects list
+ */
+
+struct sieve_side_effects_list *
+sieve_side_effects_list_create(struct sieve_result *result);
+void sieve_side_effects_list_add(struct sieve_side_effects_list *list,
+ const struct sieve_side_effect *seffect);
+
+/*
+ * Error handling
+ */
+
+void sieve_result_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_error(aenv, ...) \
+ sieve_result_error(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_global_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_global_error(aenv, ...) \
+ sieve_result_global_error(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_warning(aenv, ...) \
+ sieve_result_warning(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_global_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_global_warning(aenv, ...) \
+ sieve_result_global_warning(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_log(aenv, ...) \
+ sieve_result_log(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_global_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_global_log(aenv, ...) \
+ sieve_result_global_log(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_global_log_error(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_global_log_error(aenv, ...) \
+ sieve_result_global_log_error(aenv, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_result_global_log_warning(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ const char *fmt, ...)
+ ATTR_FORMAT(4, 5);
+#define sieve_result_global_log_warning(aenv, ...) \
+ sieve_result_global_log_warning(aenv, __FILE__, __LINE__, __VA_ARGS__)
+
+void sieve_result_event_log(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, struct event *event,
+ const char *fmt, ...) ATTR_FORMAT(5, 0);
+#define sieve_result_event_log(aenv, event, ...) \
+ sieve_result_event_log(aenv, __FILE__, __LINE__, event, __VA_ARGS__)
+
+void sieve_result_critical(const struct sieve_action_exec_env *aenv,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ const char *user_prefix, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_result_critical(aenv, ...) \
+ sieve_result_critical(aenv, __FILE__, __LINE__, __VA_ARGS__)
+int sieve_result_mail_error(const struct sieve_action_exec_env *aenv,
+ struct mail *mail,
+ const char *csrc_filename,
+ unsigned int csrc_linenum, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_result_mail_error(aenv, mail, ...) \
+ sieve_result_mail_error(aenv, mail, __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-runtime-trace.c b/pigeonhole/src/lib-sieve/sieve-runtime-trace.c
new file mode 100644
index 0000000..a9351a3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-runtime-trace.c
@@ -0,0 +1,151 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "ostream.h"
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime.h"
+#include "sieve-runtime-trace.h"
+
+static inline string_t *_trace_line_new
+(const struct sieve_runtime_env *renv, sieve_size_t address, unsigned int cmd_line)
+{
+ string_t *trline;
+ unsigned int i;
+
+ trline = t_str_new(128);
+ if ( (renv->trace->config.flags & SIEVE_TRFLG_ADDRESSES) > 0 )
+ str_printfa(trline, "%08llx: ", (unsigned long long) address);
+ if ( cmd_line > 0 )
+ str_printfa(trline, "%4d: ", cmd_line);
+ else
+ str_append(trline, " ");
+
+ for ( i = 0; i < renv->trace->indent; i++ )
+ str_append(trline, " ");
+
+ return trline;
+}
+
+static inline void _trace_line_print
+(string_t *trline, const struct sieve_runtime_env *renv)
+{
+ sieve_trace_log_write_line(renv->trace->log, trline);
+}
+
+static inline void _trace_line_print_empty
+(const struct sieve_runtime_env *renv)
+{
+ sieve_trace_log_write_line(renv->trace->log, NULL);
+}
+
+/*
+ * Trace errors
+ */
+
+void _sieve_runtime_trace_error
+(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+{
+ string_t *trline = _trace_line_new(renv, renv->pc, 0);
+
+ str_printfa(trline, "%s: #ERROR#: ", sieve_operation_mnemonic(renv->oprtn));
+ str_vprintfa(trline, fmt, args);
+
+ _trace_line_print(trline, renv);
+}
+
+void _sieve_runtime_trace_operand_error
+(const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ const char *fmt, va_list args)
+{
+ string_t *trline = _trace_line_new(renv, oprnd->address,
+ sieve_runtime_get_source_location(renv, oprnd->address));
+
+ str_printfa(trline, "%s: #ERROR#: ", sieve_operation_mnemonic(renv->oprtn));
+
+ if ( oprnd->field_name != NULL )
+ str_printfa(trline, "%s: ", oprnd->field_name);
+
+ str_vprintfa(trline, fmt, args);
+
+ _trace_line_print(trline, renv);
+}
+
+/*
+ * Trace info
+ */
+
+static inline void ATTR_FORMAT(4, 0) _sieve_runtime_trace_vprintf
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+ unsigned int cmd_line, const char *fmt, va_list args)
+{
+ string_t *trline = _trace_line_new(renv, address, cmd_line);
+
+ str_vprintfa(trline, fmt, args);
+
+ _trace_line_print(trline, renv);
+}
+
+static inline void ATTR_FORMAT(4, 5) _sieve_runtime_trace_printf
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+ unsigned int cmd_line, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ _sieve_runtime_trace_vprintf(renv, address, cmd_line, fmt, args);
+ va_end(args);
+}
+
+void ATTR_FORMAT(2, 0) _sieve_runtime_trace
+(const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+{
+ _sieve_runtime_trace_vprintf
+ (renv, renv->oprtn->address, sieve_runtime_get_command_location(renv),
+ fmt, args);
+}
+
+void _sieve_runtime_trace_address
+(const struct sieve_runtime_env *renv, sieve_size_t address,
+ const char *fmt, va_list args)
+{
+ _sieve_runtime_trace_vprintf
+ (renv, address, sieve_runtime_get_source_location(renv, address), fmt,
+ args);
+}
+
+/*
+ * Trace boundaries
+ */
+
+void _sieve_runtime_trace_begin(const struct sieve_runtime_env *renv)
+{
+ const char *script_name = ( renv->script != NULL ?
+ sieve_script_name(renv->script) : sieve_binary_path(renv->sbin) );
+
+ _trace_line_print_empty(renv);
+ _sieve_runtime_trace_printf(renv, renv->pc, 0,
+ "## Started executing script '%s'", script_name);
+}
+
+void _sieve_runtime_trace_end(const struct sieve_runtime_env *renv)
+{
+ const char *script_name = ( renv->script != NULL ?
+ sieve_script_name(renv->script) : sieve_binary_path(renv->sbin) );
+
+ _sieve_runtime_trace_printf(renv, renv->pc, 0,
+ "## Finished executing script '%s'", script_name);
+ _trace_line_print_empty(renv);
+}
+
+void _sieve_runtime_trace_sep(const struct sieve_runtime_env *renv)
+{
+ _trace_line_print_empty(renv);
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-runtime-trace.h b/pigeonhole/src/lib-sieve/sieve-runtime-trace.h
new file mode 100644
index 0000000..b165586
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-runtime-trace.h
@@ -0,0 +1,182 @@
+#ifndef SIEVE_RUNTIME_TRACE_H
+#define SIEVE_RUNTIME_TRACE_H
+
+#include "sieve-common.h"
+#include "sieve-runtime.h"
+
+/*
+ * Runtime trace
+ */
+
+struct sieve_runtime_trace {
+ struct sieve_trace_config config;
+ struct sieve_trace_log *log;
+ unsigned int indent;
+};
+
+/* Trace configuration */
+
+static inline bool sieve_runtime_trace_active
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level)
+{
+ return ( renv->trace != NULL && trace_level <= renv->trace->config.level );
+}
+
+static inline bool sieve_runtime_trace_hasflag
+(const struct sieve_runtime_env *renv, unsigned int flag)
+{
+ return ( renv->trace != NULL && (renv->trace->config.flags & flag) != 0 );
+}
+
+/* Trace indent */
+
+static inline void sieve_runtime_trace_descend
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL ) renv->trace->indent++;
+}
+
+static inline void sieve_runtime_trace_ascend
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL ) renv->trace->indent--;
+}
+
+static inline void sieve_runtime_trace_toplevel
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL ) renv->trace->indent = 0;
+}
+
+/* Trace errors */
+
+void _sieve_runtime_trace_error
+ (const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+ ATTR_FORMAT(2, 0);
+
+void _sieve_runtime_trace_operand_error
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+
+static inline void sieve_runtime_trace_error
+ (const struct sieve_runtime_env *renv, const char *fmt, ...)
+ ATTR_FORMAT(2, 3);
+
+static inline void sieve_runtime_trace_operand_error
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+static inline void ATTR_FORMAT(2, 3) sieve_runtime_trace_error
+ (const struct sieve_runtime_env *renv, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ if ( renv->trace != NULL )
+ _sieve_runtime_trace_error(renv, fmt, args);
+ va_end(args);
+}
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace_operand_error
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ if ( renv->trace != NULL )
+ _sieve_runtime_trace_operand_error(renv, oprnd, fmt, args);
+ va_end(args);
+}
+
+/* Trace info */
+
+void _sieve_runtime_trace
+ (const struct sieve_runtime_env *renv, const char *fmt, va_list args)
+ ATTR_FORMAT(2, 0);
+
+static inline void sieve_runtime_trace
+ (const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+ const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+
+ if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+ _sieve_runtime_trace(renv, fmt, args);
+ }
+
+ va_end(args);
+}
+
+void _sieve_runtime_trace_address
+ (const struct sieve_runtime_env *renv, sieve_size_t address,
+ const char *fmt, va_list args) ATTR_FORMAT(3, 0);
+
+static inline void sieve_runtime_trace_address
+ (const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+ sieve_size_t address, const char *fmt, ...) ATTR_FORMAT(4, 5);
+
+static inline void ATTR_FORMAT(4, 5) sieve_runtime_trace_address
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+ sieve_size_t address, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+
+ if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+ _sieve_runtime_trace_address(renv, address, fmt, args);
+ }
+
+ va_end(args);
+}
+
+static inline void ATTR_FORMAT(3, 4) sieve_runtime_trace_here
+(const struct sieve_runtime_env *renv, sieve_trace_level_t trace_level,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+
+ if ( renv->trace != NULL && trace_level <= renv->trace->config.level ) {
+ _sieve_runtime_trace_address(renv, renv->pc, fmt, args);
+ }
+
+ va_end(args);
+}
+
+/* Trace boundaries */
+
+void _sieve_runtime_trace_begin(const struct sieve_runtime_env *renv);
+void _sieve_runtime_trace_end(const struct sieve_runtime_env *renv);
+void _sieve_runtime_trace_sep(const struct sieve_runtime_env *renv);
+
+static inline void sieve_runtime_trace_begin
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL )
+ _sieve_runtime_trace_begin(renv);
+}
+
+static inline void sieve_runtime_trace_end
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL )
+ _sieve_runtime_trace_end(renv);
+}
+
+static inline void sieve_runtime_trace_sep
+(const struct sieve_runtime_env *renv)
+{
+ if ( renv->trace != NULL )
+ _sieve_runtime_trace_sep(renv);
+}
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-runtime.h b/pigeonhole/src/lib-sieve/sieve-runtime.h
new file mode 100644
index 0000000..fa50602
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-runtime.h
@@ -0,0 +1,40 @@
+#ifndef SIEVE_RUNTIME_H
+#define SIEVE_RUNTIME_H
+
+#include "sieve-common.h"
+#include "sieve-execute.h"
+
+/*
+ * Runtime environment
+ */
+
+struct sieve_runtime_env {
+ const struct sieve_execute_env *exec_env;
+ struct event *event;
+
+ /* Interpreter */
+ struct sieve_interpreter *interp;
+ struct sieve_error_handler *ehandler;
+
+ /* Executing script */
+ struct sieve_script *script;
+
+ /* Executing binary */
+ struct sieve_binary *sbin;
+ struct sieve_binary_block *sblock;
+
+ /* Current code */
+ sieve_size_t pc;
+ const struct sieve_operation *oprtn;
+
+ /* Tested message */
+ struct sieve_message_context *msgctx;
+
+ /* Filter result */
+ struct sieve_result *result;
+
+ /* Runtime tracing */
+ struct sieve_runtime_trace *trace;
+};
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-script-private.h b/pigeonhole/src/lib-sieve/sieve-script-private.h
new file mode 100644
index 0000000..7f23c19
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-script-private.h
@@ -0,0 +1,100 @@
+#ifndef SIEVE_SCRIPT_PRIVATE_H
+#define SIEVE_SCRIPT_PRIVATE_H
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+
+/*
+ * Script object
+ */
+
+struct sieve_script_vfuncs {
+ void (*destroy)(struct sieve_script *script);
+
+ int (*open)(struct sieve_script *script, enum sieve_error *error_r);
+
+ int (*get_stream)(struct sieve_script *script,
+ struct istream **stream_r, enum sieve_error *error_r);
+
+ /* binary */
+ int (*binary_read_metadata)(struct sieve_script *_script,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset);
+ void (*binary_write_metadata)(struct sieve_script *script,
+ struct sieve_binary_block *sblock);
+ bool (*binary_dump_metadata)(struct sieve_script *script,
+ struct sieve_dumptime_env *denv,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset);
+ struct sieve_binary *(*binary_load)(struct sieve_script *script,
+ enum sieve_error *error_r);
+ int (*binary_save)(struct sieve_script *script,
+ struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r);
+ const char *(*binary_get_prefix)(struct sieve_script *script);
+
+ /* management */
+ int (*rename)(struct sieve_script *script, const char *newname);
+ int (*delete)(struct sieve_script *script);
+ int (*is_active)(struct sieve_script *script);
+ int (*activate)(struct sieve_script *script);
+
+ /* properties */
+ int (*get_size)(const struct sieve_script *script, uoff_t *size_r);
+
+ /* matching */
+ bool (*equals)(const struct sieve_script *script,
+ const struct sieve_script *other);
+};
+
+struct sieve_script {
+ pool_t pool;
+ unsigned int refcount;
+ struct sieve_storage *storage;
+ struct event *event;
+
+ const char *driver_name;
+ const struct sieve_script *script_class;
+ struct sieve_script_vfuncs v;
+
+ const char *name;
+ const char *location;
+
+ /* Stream */
+ struct istream *stream;
+
+ bool open:1;
+};
+
+void sieve_script_init(struct sieve_script *script,
+ struct sieve_storage *storage,
+ const struct sieve_script *script_class,
+ const char *location, const char *name);
+
+/*
+ * Built-in script drivers
+ */
+
+extern const struct sieve_script sieve_data_script;
+extern const struct sieve_script sieve_file_script;
+extern const struct sieve_script sieve_dict_script;
+extern const struct sieve_script sieve_ldap_script;
+
+/*
+ * Error handling
+ */
+
+void sieve_script_set_error(struct sieve_script *script, enum sieve_error error,
+ const char *fmt, ...) ATTR_FORMAT(3, 4);
+void sieve_script_set_internal_error(struct sieve_script *script);
+void sieve_script_set_critical(struct sieve_script *script,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+/*
+ * Script sequence
+ */
+
+void sieve_script_sequence_init(struct sieve_script_sequence *seq,
+ struct sieve_storage *storage);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-script.c b/pigeonhole/src/lib-sieve/sieve-script.c
new file mode 100644
index 0000000..32963a0
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-script.c
@@ -0,0 +1,906 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "compat.h"
+#include "unichar.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "hash.h"
+#include "array.h"
+#include "eacces-error.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-settings.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-storage-private.h"
+#include "sieve-script-private.h"
+
+/*
+ * Script name
+ */
+
+bool sieve_script_name_is_valid(const char *scriptname)
+{
+ ARRAY_TYPE(unichars) uni_name;
+ unsigned int count, i;
+ const unichar_t *name_chars;
+ size_t namelen = strlen(scriptname);
+
+ /* Check minimum length */
+ if (namelen == 0)
+ return FALSE;
+
+ /* Check worst-case maximum length */
+ if (namelen > SIEVE_MAX_SCRIPT_NAME_LEN * 4)
+ return FALSE;
+
+ /* Intialize array for unicode characters */
+ t_array_init(&uni_name, namelen * 4);
+
+ /* Convert UTF-8 to UCS4/UTF-32 */
+ if (uni_utf8_to_ucs4(scriptname, &uni_name) < 0)
+ return FALSE;
+ name_chars = array_get(&uni_name, &count);
+
+ /* Check true maximum length */
+ if (count > SIEVE_MAX_SCRIPT_NAME_LEN)
+ return FALSE;
+
+ /* Scan name for invalid characters
+ * FIXME: compliance with Net-Unicode Definition (Section 2 of
+ * RFC 5198) is not checked fully and no normalization
+ * is performed.
+ */
+ for (i = 0; i < count; i++) {
+ /* 0000-001F; [CONTROL CHARACTERS] */
+ if (name_chars[i] <= 0x001f)
+ return FALSE;
+ /* 002F; SLASH (not RFC-prohibited, but '/' is dangerous) */
+ if (name_chars[i] == 0x002f)
+ return FALSE;
+ /* 007F; DELETE */
+ if (name_chars[i] == 0x007f)
+ return FALSE;
+ /* 0080-009F; [CONTROL CHARACTERS] */
+ if (name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f)
+ return FALSE;
+ /* 00FF */
+ if (name_chars[i] == 0x00ff)
+ return FALSE;
+ /* 2028; LINE SEPARATOR */
+ /* 2029; PARAGRAPH SEPARATOR */
+ if (name_chars[i] == 0x2028 || name_chars[i] == 0x2029)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Script instance
+ */
+
+void sieve_script_init(struct sieve_script *script,
+ struct sieve_storage *storage,
+ const struct sieve_script *script_class,
+ const char *location, const char *name)
+{
+ i_assert(storage != NULL);
+
+ script->script_class = script_class;
+ script->refcount = 1;
+ script->storage = storage;
+ script->location = p_strdup_empty(script->pool, location);
+ script->name = p_strdup(script->pool, name);
+
+ script->event = event_create(storage->event);
+ event_add_str(script->event, "script_name", name);
+ event_add_str(script->event, "script_location", location);
+ if (name == NULL)
+ event_set_append_log_prefix(script->event, "script: ");
+ else {
+ event_set_append_log_prefix(
+ script->event, t_strdup_printf("script `%s': ", name));
+ }
+
+ sieve_storage_ref(storage);
+}
+
+struct sieve_script *
+sieve_script_create(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ struct sieve_script *script;
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ storage = sieve_storage_create(svinst, location, 0, error_r);
+ if (storage == NULL)
+ return NULL;
+
+ script = sieve_storage_get_script(storage, name, error_r);
+
+ sieve_storage_unref(&storage);
+ return script;
+}
+
+void sieve_script_ref(struct sieve_script *script)
+{
+ script->refcount++;
+}
+
+void sieve_script_unref(struct sieve_script **_script)
+{
+ struct sieve_script *script = *_script;
+
+ *_script = NULL;
+
+ if (script == NULL)
+ return;
+
+ i_assert(script->refcount > 0);
+ if (--script->refcount != 0)
+ return;
+
+ if (script->stream != NULL) {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ set_name("sieve_script_closed");
+ e_debug(e->event(), "Closed script");
+ }
+ i_stream_unref(&script->stream);
+
+ if (script->v.destroy != NULL)
+ script->v.destroy(script);
+
+ sieve_storage_unref(&script->storage);
+ event_unref(&script->event);
+ pool_unref(&script->pool);
+}
+
+int sieve_script_open(struct sieve_script *script, enum sieve_error *error_r)
+{
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ if (script->open)
+ return 0;
+
+ if (script->v.open(script, error_r) < 0)
+ return -1;
+
+ i_assert(script->location != NULL);
+ i_assert(script->name != NULL);
+ script->open = TRUE;
+
+ if (*script->name != '\0') {
+ e_debug(script->event, "Opened script `%s' from `%s'",
+ script->name, script->location);
+ } else {
+ e_debug(script->event, "Opened nameless script from `%s'",
+ script->location);
+ }
+ return 0;
+}
+
+int sieve_script_open_as(struct sieve_script *script, const char *name,
+ enum sieve_error *error_r)
+{
+ if (sieve_script_open(script, error_r) < 0)
+ return -1;
+
+ /* override name */
+ script->name = p_strdup(script->pool, name);
+ event_add_str(script->event, "script_name", name);
+ return 0;
+}
+
+struct sieve_script *
+sieve_script_create_open(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+
+ script = sieve_script_create(svinst, location, name, error_r);
+ if (script == NULL)
+ return NULL;
+
+ if (sieve_script_open(script, error_r) < 0) {
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return script;
+}
+
+int sieve_script_check(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+ enum sieve_error error;
+
+ if (error_r == NULL)
+ error_r = &error;
+
+ script = sieve_script_create_open(svinst, location, name, error_r);
+ if (script == NULL)
+ return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+
+ sieve_script_unref(&script);
+ return 1;
+}
+
+/*
+ * Properties
+ */
+
+const char *sieve_script_name(const struct sieve_script *script)
+{
+ return script->name;
+}
+
+const char *sieve_script_location(const struct sieve_script *script)
+{
+ return script->location;
+}
+
+struct sieve_instance *sieve_script_svinst(const struct sieve_script *script)
+{
+ return script->storage->svinst;
+}
+
+int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r)
+{
+ struct istream *stream;
+ int ret;
+
+ if (script->v.get_size != NULL) {
+ if ((ret = script->v.get_size(script, size_r)) != 0)
+ return ret;
+ }
+
+ /* Try getting size from the stream */
+ if (script->stream == NULL &&
+ sieve_script_get_stream(script, &stream, NULL) < 0)
+ return -1;
+
+ if (i_stream_get_size(script->stream, TRUE, size_r) < 0) {
+ sieve_storage_set_critical(script->storage,
+ "i_stream_get_size(%s) failed: %s",
+ i_stream_get_name(script->stream),
+ i_stream_get_error(script->stream));
+ return -1;
+ }
+ return 0;
+}
+
+bool sieve_script_is_open(const struct sieve_script *script)
+{
+ return script->open;
+}
+
+bool sieve_script_is_default(const struct sieve_script *script)
+{
+ return script->storage->is_default;
+}
+
+/*
+ * Stream management
+ */
+
+int sieve_script_get_stream(struct sieve_script *script,
+ struct istream **stream_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = script->storage;
+ enum sieve_error error;
+ int ret;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ if (script->stream != NULL) {
+ *stream_r = script->stream;
+ return 0;
+ }
+
+ // FIXME: necessary?
+ i_assert(script->open);
+
+ T_BEGIN {
+ ret = script->v.get_stream(script, &script->stream, error_r);
+ } T_END;
+
+ if (ret < 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_script_opened");
+ e_debug(e->event(), "Failed to open script for reading: %s",
+ storage->error);
+ return -1;
+ }
+
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ set_name("sieve_script_opened");
+ e_debug(e->event(), "Opened script for reading");
+
+ *stream_r = script->stream;
+ return 0;
+}
+
+/*
+ * Comparison
+ */
+
+bool sieve_script_equals(const struct sieve_script *script,
+ const struct sieve_script *other)
+{
+ if (script == other)
+ return TRUE;
+ if (script == NULL || other == NULL)
+ return FALSE;
+ if (script->script_class != other->script_class)
+ return FALSE;
+
+ if (script->v.equals == NULL) {
+ i_assert (script->location != NULL && other->location != NULL);
+
+ return (strcmp(script->location, other->location) == 0);
+ }
+
+ return script->v.equals(script, other);
+}
+
+unsigned int sieve_script_hash(const struct sieve_script *script)
+{
+ i_assert(script->name != NULL);
+
+ return str_hash(script->name);
+}
+
+/*
+ * Binary
+ */
+
+int sieve_script_binary_read_metadata(struct sieve_script *script,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset)
+{
+ struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+ string_t *storage_class, *location;
+ unsigned int version;
+
+ if ((sieve_binary_block_get_size(sblock) - *offset) == 0)
+ return 0;
+
+ /* storage class */
+ if (!sieve_binary_read_string(sblock, offset, &storage_class)) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s': "
+ "Invalid storage class",
+ sieve_binary_path(sbin), script->location);
+ return -1;
+ }
+ if (strcmp(str_c(storage_class), script->driver_name) != 0) {
+ e_debug(script->event,
+ "Binary `%s' reports unexpected driver name for script `%s' "
+ "(`%s' rather than `%s')",
+ sieve_binary_path(sbin), script->location,
+ str_c(storage_class), script->driver_name);
+ return 0;
+ }
+
+ /* version */
+ if (!sieve_binary_read_unsigned(sblock, offset, &version)) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s': "
+ "Invalid version",
+ sieve_binary_path(sbin), script->location);
+ return -1;
+ }
+ if (script->storage->version != version) {
+ e_debug(script->event,
+ "Binary `%s' was compiled with "
+ "a different version of the `%s' script storage class "
+ "(compiled v%d, expected v%d; "
+ "automatically fixed when re-compiled)",
+ sieve_binary_path(sbin), script->driver_name,
+ version, script->storage->version);
+ return 0;
+ }
+
+ /* location */
+ if (!sieve_binary_read_string(sblock, offset, &location)) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s': "
+ "Invalid location",
+ sieve_binary_path(sbin), script->location);
+ return -1;
+ }
+ i_assert(script->location != NULL);
+ if (strcmp(str_c(location), script->location) != 0) {
+ e_debug(script->event,
+ "Binary `%s' reports different location "
+ "for script `%s' (binary points to `%s')",
+ sieve_binary_path(sbin), script->location,
+ str_c(location));
+ return 0;
+ }
+
+ if (script->v.binary_read_metadata == NULL)
+ return 1;
+
+ return script->v.binary_read_metadata(script, sblock, offset);
+}
+
+void sieve_script_binary_write_metadata(struct sieve_script *script,
+ struct sieve_binary_block *sblock)
+{
+ sieve_binary_emit_cstring(sblock, script->driver_name);
+ sieve_binary_emit_unsigned(sblock, script->storage->version);
+ sieve_binary_emit_cstring(sblock, (script->location == NULL ?
+ "" : script->location));
+
+ if (script->v.binary_write_metadata == NULL)
+ return;
+
+ script->v.binary_write_metadata(script, sblock);
+}
+
+bool sieve_script_binary_dump_metadata(struct sieve_script *script,
+ struct sieve_dumptime_env *denv,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset)
+{
+ struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+ struct sieve_instance *svinst = sieve_binary_svinst(sbin);
+ string_t *storage_class, *location;
+ struct sieve_script *adhoc_script = NULL;
+ unsigned int version;
+ bool result = TRUE;
+
+ /* storage class */
+ if (!sieve_binary_read_string(sblock, offset, &storage_class))
+ return FALSE;
+ sieve_binary_dumpf(denv, "class = %s\n", str_c(storage_class));
+
+ /* version */
+ if (!sieve_binary_read_unsigned(sblock, offset, &version))
+ return FALSE;
+ sieve_binary_dumpf(denv, "class.version = %d\n", version);
+
+ /* location */
+ if (!sieve_binary_read_string(sblock, offset, &location))
+ return FALSE;
+ sieve_binary_dumpf(denv, "location = %s\n", str_c(location));
+
+ if (script == NULL) {
+ script = adhoc_script =
+ sieve_script_create(svinst, str_c(location),
+ NULL, NULL);
+ }
+
+ if (script != NULL && script->v.binary_dump_metadata != NULL) {
+ result = script->v.binary_dump_metadata(
+ script, denv, sblock, offset);
+ }
+
+ if (adhoc_script != NULL)
+ sieve_script_unref(&adhoc_script);
+ return result;
+}
+
+struct sieve_binary *
+sieve_script_binary_load(struct sieve_script *script, enum sieve_error *error_r)
+{
+ if (script->v.binary_load == NULL) {
+ *error_r = SIEVE_ERROR_NOT_POSSIBLE;
+ return NULL;
+ }
+
+ return script->v.binary_load(script, error_r);
+}
+
+int sieve_script_binary_save(struct sieve_script *script,
+ struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r)
+{
+ struct sieve_script *bin_script = sieve_binary_script(sbin);
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ i_assert(bin_script == NULL || sieve_script_equals(bin_script, script));
+
+ if (script->v.binary_save == NULL) {
+ *error_r = SIEVE_ERROR_NOT_POSSIBLE;
+ return -1;
+ }
+
+ return script->v.binary_save(script, sbin, update, error_r);
+}
+
+const char *sieve_script_binary_get_prefix(struct sieve_script *script)
+{
+ struct sieve_storage *storage = script->storage;
+
+ if (storage->bin_dir != NULL &&
+ sieve_storage_setup_bindir(storage, 0700) >= 0) {
+ return t_strconcat(storage->bin_dir, "/", script->name, NULL);
+ }
+
+ if (script->v.binary_get_prefix == NULL)
+ return NULL;
+
+ return script->v.binary_get_prefix(script);
+}
+
+/*
+ * Management
+ */
+
+static int
+sieve_script_copy_from_default(struct sieve_script *script, const char *newname)
+{
+ struct sieve_storage *storage = script->storage;
+ struct istream *input;
+ int ret;
+
+ /* copy from default */
+ if ((ret = sieve_script_open(script, NULL)) < 0 ||
+ (ret = sieve_script_get_stream(script, &input, NULL)) < 0) {
+ sieve_storage_copy_error(storage->default_for, storage);
+ return ret;
+ }
+
+ ret = sieve_storage_save_as(storage->default_for, input, newname);
+ if (ret < 0) {
+ sieve_storage_copy_error(storage, storage->default_for);
+ } else if (sieve_script_is_active(script) > 0) {
+ struct sieve_script *newscript;
+ enum sieve_error error;
+
+ newscript = sieve_storage_open_script(storage->default_for,
+ newname, &error);
+ if (newscript == NULL) {
+ /* Somehow not actually saved */
+ ret = (error == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+ } else if (sieve_script_activate(newscript, (time_t)-1) < 0) {
+ /* Failed to activate; roll back */
+ ret = -1;
+ (void)sieve_script_delete(newscript, TRUE);
+ }
+ if (newscript != NULL)
+ sieve_script_unref(&newscript);
+
+ if (ret < 0) {
+ e_error(storage->event,
+ "Failed to implicitly activate script `%s' "
+ "after rename", newname);
+ sieve_storage_copy_error(storage->default_for, storage);
+ }
+ }
+
+ return ret;
+}
+
+int sieve_script_rename(struct sieve_script *script, const char *newname)
+{
+ struct sieve_storage *storage = script->storage;
+ const char *oldname = script->name;
+ struct event_passthrough *event;
+ int ret;
+
+ i_assert(newname != NULL);
+
+ /* Check script name */
+ if (!sieve_script_name_is_valid(newname)) {
+ sieve_script_set_error(script,
+ SIEVE_ERROR_BAD_PARAMS,
+ "Invalid new Sieve script name `%s'.",
+ str_sanitize(newname, 80));
+ return -1;
+ }
+
+ i_assert(script->open); // FIXME: auto-open?
+
+ if (storage->default_for == NULL) {
+ i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0);
+
+ /* rename script */
+ i_assert(script->v.rename != NULL);
+ ret = script->v.rename(script, newname);
+
+ /* rename INBOX mailbox attribute */
+ if (ret >= 0 && oldname != NULL) {
+ (void)sieve_storage_sync_script_rename(storage, oldname,
+ newname);
+ }
+ } else if (sieve_storage_check_script(storage->default_for,
+ newname, NULL) > 0) {
+ sieve_script_set_error(script, SIEVE_ERROR_EXISTS,
+ "A sieve script with that name already exists.");
+ sieve_storage_copy_error(storage->default_for, storage);
+ ret = -1;
+ } else {
+ ret = sieve_script_copy_from_default(script, newname);
+ }
+
+ event = event_create_passthrough(script->event)->
+ clear_field("script_name")->
+ add_str("old_script_name", script->name)->
+ add_str("new_script_name", newname)->
+ set_name("sieve_script_renamed");
+
+ if (ret >= 0) {
+ e_debug(event->event(), "Script renamed to `%s'", newname);
+ } else {
+ event = event->add_str("error", storage->error);
+
+ e_debug(event->event(), "Failed to rename script: %s",
+ storage->error);
+ }
+
+ return ret;
+}
+
+int sieve_script_delete(struct sieve_script *script, bool ignore_active)
+{
+ struct sieve_storage *storage = script->storage;
+ bool is_active = FALSE;
+ int ret = 0;
+
+ i_assert(script->open); // FIXME: auto-open?
+
+ /* Is the requested script active? */
+ if (sieve_script_is_active(script) > 0) {
+ is_active = TRUE;
+ if (!ignore_active) {
+ sieve_script_set_error(script, SIEVE_ERROR_ACTIVE,
+ "Cannot delete the active Sieve script.");
+ if (storage->default_for != NULL) {
+ sieve_storage_copy_error(storage->default_for,
+ storage);
+ }
+ return -1;
+ }
+ }
+
+ /* Trying to delete the default script? */
+ if (storage->is_default) {
+ /* ignore */
+ return 0;
+ }
+
+ i_assert((script->storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0);
+
+ /* Deactivate it explicity */
+ if (ignore_active && is_active)
+ (void)sieve_storage_deactivate(storage, (time_t)-1);
+
+ i_assert(script->v.delete != NULL);
+ ret = script->v.delete(script);
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ set_name("sieve_script_deleted");
+ e_debug(e->event(), "Script deleted");
+
+ /* unset INBOX mailbox attribute */
+ (void)sieve_storage_sync_script_delete(storage, script->name);
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_script_deleted");
+ e_debug(e->event(), "Failed to delete script: %s",
+ storage->error);
+ }
+ return ret;
+}
+
+int sieve_script_is_active(struct sieve_script *script)
+{
+ struct sieve_storage *storage = script->storage;
+
+ /* Special handling if this is a default script */
+ if (storage->default_for != NULL) {
+ int ret = sieve_storage_active_script_is_default(
+ storage->default_for);
+ if (ret < 0)
+ sieve_storage_copy_error(storage, storage->default_for);
+ return ret;
+ }
+
+ if (script->v.is_active == NULL)
+ return 0;
+ return script->v.is_active(script);
+}
+
+int sieve_script_activate(struct sieve_script *script, time_t mtime)
+{
+ struct sieve_storage *storage = script->storage;
+ int ret = 0;
+
+ i_assert(script->open); // FIXME: auto-open?
+
+ if (storage->default_for == NULL) {
+ i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0);
+
+ i_assert(script->v.activate != NULL);
+ ret = script->v.activate(script);
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ set_name("sieve_script_activated");
+ e_debug(e->event(), "Script activated");
+
+ sieve_storage_set_modified(storage, mtime);
+ (void)sieve_storage_sync_script_activate(storage);
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(script->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_script_activated");
+ e_debug(e->event(), "Failed to activate script: %s",
+ storage->error);
+ }
+
+ } else {
+ /* Activating the default script is equal to deactivating
+ the storage */
+ ret = sieve_storage_deactivate(storage->default_for,
+ (time_t)-1);
+ if (ret < 0)
+ sieve_storage_copy_error(storage, storage->default_for);
+ }
+
+ return ret;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_script_set_error(struct sieve_script *script, enum sieve_error error,
+ const char *fmt, ...)
+{
+ struct sieve_storage *storage = script->storage;
+ va_list va;
+
+ sieve_storage_clear_error(storage);
+
+ if (fmt != NULL) {
+ va_start(va, fmt);
+ storage->error = i_strdup_vprintf(fmt, va);
+ va_end(va);
+ }
+ storage->error_code = error;
+}
+
+void sieve_script_set_internal_error(struct sieve_script *script)
+{
+ sieve_storage_set_internal_error(script->storage);
+}
+
+void sieve_script_set_critical(struct sieve_script *script,
+ const char *fmt, ...)
+{
+ struct sieve_storage *storage = script->storage;
+
+ va_list va;
+
+ if (fmt != NULL) {
+ if ((storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0) {
+ va_start(va, fmt);
+ e_error(script->event, "%s", t_strdup_vprintf(fmt, va));
+ va_end(va);
+
+ sieve_storage_set_internal_error(storage);
+
+ } else {
+ sieve_storage_clear_error(storage);
+
+ /* no user is involved while synchronizing, so do it the
+ normal way */
+ va_start(va, fmt);
+ storage->error = i_strdup_vprintf(fmt, va);
+ va_end(va);
+
+ storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+ }
+ }
+}
+
+const char *
+sieve_script_get_last_error(struct sieve_script *script,
+ enum sieve_error *error_r)
+{
+ return sieve_storage_get_last_error(script->storage, error_r);
+}
+
+const char *sieve_script_get_last_error_lcase(struct sieve_script *script)
+{
+ return sieve_error_from_external(script->storage->error);
+}
+
+/*
+ * Script sequence
+ */
+
+void sieve_script_sequence_init(struct sieve_script_sequence *seq,
+ struct sieve_storage *storage)
+{
+ seq->storage = storage;
+ sieve_storage_ref(storage);
+}
+
+struct sieve_script_sequence *
+sieve_script_sequence_create(struct sieve_instance *svinst,
+ const char *location, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ struct sieve_script_sequence *seq;
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ storage = sieve_storage_create(svinst, location, 0, error_r);
+ if (storage == NULL)
+ return NULL;
+
+ seq = sieve_storage_get_script_sequence(storage, error_r);
+
+ sieve_storage_unref(&storage);
+ return seq;
+}
+
+struct sieve_script *
+sieve_script_sequence_next(struct sieve_script_sequence *seq,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = seq->storage;
+
+ i_assert(storage->v.script_sequence_next != NULL);
+ return storage->v.script_sequence_next(seq, error_r);
+}
+
+void sieve_script_sequence_free(struct sieve_script_sequence **_seq)
+{
+ struct sieve_script_sequence *seq = *_seq;
+ struct sieve_storage *storage = seq->storage;
+
+ if (storage->v.script_sequence_destroy != NULL)
+ storage->v.script_sequence_destroy(seq);
+
+ sieve_storage_unref(&storage);
+ *_seq = NULL;
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-script.h b/pigeonhole/src/lib-sieve/sieve-script.h
new file mode 100644
index 0000000..8ee53f4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-script.h
@@ -0,0 +1,163 @@
+#ifndef SIEVE_SCRIPT_H
+#define SIEVE_SCRIPT_H
+
+#include "sieve-common.h"
+
+#include <sys/types.h>
+
+
+/*
+ * Sieve script name
+ */
+
+bool sieve_script_name_is_valid(const char *scriptname);
+
+/*
+ * Sieve script file
+ */
+
+bool sieve_script_file_has_extension(const char *filename);
+
+/*
+ * Sieve script class
+ */
+
+void sieve_script_class_register(struct sieve_instance *svinst,
+ const struct sieve_script *script_class);
+void sieve_script_class_unregister(struct sieve_instance *svinst,
+ const struct sieve_script *script_class);
+
+/*
+ * Sieve script instance
+ */
+
+struct sieve_script;
+
+ARRAY_DEFINE_TYPE(sieve_script, struct sieve_script *);
+
+struct sieve_script *
+sieve_script_create(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r) ATTR_NULL(3,4);
+
+void sieve_script_ref(struct sieve_script *script);
+void sieve_script_unref(struct sieve_script **script);
+
+int sieve_script_open(struct sieve_script *script, enum sieve_error *error_r)
+ ATTR_NULL(2);
+int sieve_script_open_as(struct sieve_script *script, const char *name,
+ enum sieve_error *error_r) ATTR_NULL(3);
+
+struct sieve_script *
+sieve_script_create_open(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r)
+ ATTR_NULL(3, 4);
+int sieve_script_check(struct sieve_instance *svinst, const char *location,
+ const char *name, enum sieve_error *error_r)
+ ATTR_NULL(3, 4);
+
+/*
+ * Data script
+ */
+
+struct sieve_script *
+sieve_data_script_create_from_input(struct sieve_instance *svinst,
+ const char *name, struct istream *input);
+
+/*
+ * Binary
+ */
+
+int sieve_script_binary_read_metadata(struct sieve_script *script,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset);
+void sieve_script_binary_write_metadata(struct sieve_script *script,
+ struct sieve_binary_block *sblock);
+bool sieve_script_binary_dump_metadata(struct sieve_script *script,
+ struct sieve_dumptime_env *denv,
+ struct sieve_binary_block *sblock,
+ sieve_size_t *offset) ATTR_NULL(1);
+
+struct sieve_binary *
+sieve_script_binary_load(struct sieve_script *script,
+ enum sieve_error *error_r);
+int sieve_script_binary_save(struct sieve_script *script,
+ struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r) ATTR_NULL(4);
+
+const char *sieve_script_binary_get_prefix(struct sieve_script *script);
+
+/*
+ * Stream management
+ */
+
+int sieve_script_get_stream(struct sieve_script *script,
+ struct istream **stream_r,
+ enum sieve_error *error_r) ATTR_NULL(3);
+
+/*
+ * Management
+ */
+
+// FIXME: check read/write flag!
+
+int sieve_script_rename(struct sieve_script *script, const char *newname);
+int sieve_script_is_active(struct sieve_script *script);
+int sieve_script_activate(struct sieve_script *script, time_t mtime);
+int sieve_script_delete(struct sieve_script *script, bool ignore_active);
+
+/*
+ * Properties
+ */
+
+const char *sieve_script_name(const struct sieve_script *script) ATTR_PURE;
+const char *sieve_script_location(const struct sieve_script *script) ATTR_PURE;
+struct sieve_instance *
+sieve_script_svinst(const struct sieve_script *script) ATTR_PURE;
+
+int sieve_script_get_size(struct sieve_script *script, uoff_t *size_r);
+bool sieve_script_is_open(const struct sieve_script *script) ATTR_PURE;
+bool sieve_script_is_default(const struct sieve_script *script) ATTR_PURE;
+
+const char *
+sieve_file_script_get_dirpath(const struct sieve_script *script) ATTR_PURE;
+const char *
+sieve_file_script_get_path(const struct sieve_script *script) ATTR_PURE;
+
+/*
+ * Comparison
+ */
+
+bool sieve_script_equals(const struct sieve_script *script,
+ const struct sieve_script *other);
+
+unsigned int sieve_script_hash(const struct sieve_script *script);
+static inline int
+sieve_script_cmp(const struct sieve_script *script,
+ const struct sieve_script *other)
+{
+ return ( sieve_script_equals(script, other) ? 0 : -1 );
+}
+
+/*
+ * Error handling
+ */
+
+const char *sieve_script_get_last_error(struct sieve_script *script,
+ enum sieve_error *error_r) ATTR_NULL(2);
+const char *sieve_script_get_last_error_lcase(struct sieve_script *script);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence;
+
+struct sieve_script_sequence *
+sieve_script_sequence_create(struct sieve_instance *svinst,
+ const char *location, enum sieve_error *error_r);
+struct sieve_script *
+sieve_script_sequence_next(struct sieve_script_sequence *seq,
+ enum sieve_error *error_r);
+void sieve_script_sequence_free(struct sieve_script_sequence **_seq);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-settings.c b/pigeonhole/src/lib-sieve/sieve-settings.c
new file mode 100644
index 0000000..47f70da
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-settings.c
@@ -0,0 +1,270 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-error.h"
+#include "sieve-address.h"
+#include "sieve-address-source.h"
+#include "sieve-settings.h"
+
+#include <ctype.h>
+
+/*
+ * Access to settings
+ */
+
+bool sieve_setting_get_uint_value(struct sieve_instance *svinst,
+ const char *setting,
+ unsigned long long int *value_r)
+{
+ const char *str_value;
+
+ str_value = sieve_setting_get(svinst, setting);
+
+ if (str_value == NULL || *str_value == '\0')
+ return FALSE;
+
+ if (str_to_ullong(str_value, value_r) < 0) {
+ e_warning(svinst->event,
+ "invalid unsigned integer value for setting '%s': "
+ "'%s'", setting, str_value);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool sieve_setting_get_int_value(struct sieve_instance *svinst,
+ const char *setting, long long int *value_r)
+{
+ const char *str_value;
+
+ str_value = sieve_setting_get(svinst, setting);
+ if (str_value == NULL || *str_value == '\0')
+ return FALSE;
+
+ if (str_to_llong(str_value, value_r) < 0) {
+ e_warning(svinst->event,
+ "invalid integer value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool sieve_setting_get_size_value(struct sieve_instance *svinst,
+ const char *setting, size_t *value_r)
+{
+ const char *str_value;
+ uintmax_t value, multiply = 1;
+ const char *endp;
+
+ str_value = sieve_setting_get(svinst, setting);
+ if (str_value == NULL || *str_value == '\0')
+ return FALSE;
+
+ if (str_parse_uintmax(str_value, &value, &endp) < 0) {
+ e_warning(svinst->event,
+ "invalid size value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+ switch (i_toupper(*endp)) {
+ case '\0': /* default */
+ case 'B': /* byte (useless) */
+ multiply = 1;
+ break;
+ case 'K': /* kilobyte */
+ multiply = 1024;
+ break;
+ case 'M': /* megabyte */
+ multiply = 1024*1024;
+ break;
+ case 'G': /* gigabyte */
+ multiply = 1024*1024*1024;
+ break;
+ case 'T': /* terabyte */
+ multiply = 1024ULL*1024*1024*1024;
+ break;
+ default:
+ e_warning(svinst->event,
+ "invalid size value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+
+ if (value > SSIZE_T_MAX / multiply) {
+ e_warning(svinst->event,
+ "overflowing size value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+
+ *value_r = (size_t)(value * multiply);
+ return TRUE;
+}
+
+bool sieve_setting_get_bool_value(struct sieve_instance *svinst,
+ const char *setting, bool *value_r)
+{
+ const char *str_value;
+
+ str_value = sieve_setting_get(svinst, setting);
+ if (str_value == NULL)
+ return FALSE;
+
+ str_value = t_str_trim(str_value, "\t ");
+ if (*str_value == '\0')
+ return FALSE;
+
+ if (strcasecmp(str_value, "yes") == 0) {
+ *value_r = TRUE;
+ return TRUE;
+ }
+
+ if (strcasecmp(str_value, "no") == 0) {
+ *value_r = FALSE;
+ return TRUE;
+ }
+
+ e_warning(svinst->event,
+ "invalid boolean value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+}
+
+bool sieve_setting_get_duration_value(struct sieve_instance *svinst,
+ const char *setting,
+ sieve_number_t *value_r)
+{
+ const char *str_value;
+ uintmax_t value, multiply = 1;
+ const char *endp;
+
+ str_value = sieve_setting_get(svinst, setting);
+ if (str_value == NULL)
+ return FALSE;
+
+ str_value = t_str_trim(str_value, "\t ");
+ if (*str_value == '\0')
+ return FALSE;
+
+ if (str_parse_uintmax(str_value, &value, &endp) < 0) {
+ e_warning(svinst->event,
+ "invalid duration value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+
+ switch (i_tolower(*endp)) {
+ case '\0': /* default */
+ case 's': /* seconds */
+ multiply = 1;
+ break;
+ case 'm': /* minutes */
+ multiply = 60;
+ break;
+ case 'h': /* hours */
+ multiply = 60*60;
+ break;
+ case 'd': /* days */
+ multiply = 24*60*60;
+ break;
+ default:
+ e_warning(svinst->event,
+ "invalid duration value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+
+ if (value > SIEVE_MAX_NUMBER / multiply) {
+ e_warning(svinst->event,
+ "overflowing duration value for setting '%s': '%s'",
+ setting, str_value);
+ return FALSE;
+ }
+
+ *value_r = (unsigned int)(value * multiply);
+ return TRUE;
+}
+
+/*
+ * Main Sieve engine settings
+ */
+
+void sieve_settings_load(struct sieve_instance *svinst)
+{
+ const char *str_setting, *error;
+ unsigned long long int uint_setting;
+ size_t size_setting;
+ sieve_number_t period;
+
+ svinst->max_script_size = SIEVE_DEFAULT_MAX_SCRIPT_SIZE;
+ if (sieve_setting_get_size_value(svinst, "sieve_max_script_size",
+ &size_setting))
+ svinst->max_script_size = size_setting;
+
+ svinst->max_actions = SIEVE_DEFAULT_MAX_ACTIONS;
+ if (sieve_setting_get_uint_value(svinst, "sieve_max_actions",
+ &uint_setting))
+ svinst->max_actions = (unsigned int)uint_setting;
+
+ svinst->max_redirects = SIEVE_DEFAULT_MAX_REDIRECTS;
+ if (sieve_setting_get_uint_value(svinst, "sieve_max_redirects",
+ &uint_setting))
+ svinst->max_redirects = (unsigned int)uint_setting;
+
+ svinst->max_cpu_time_secs =
+ (svinst->env_location == SIEVE_ENV_LOCATION_MS ?
+ 0 : SIEVE_DEFAULT_MAX_CPU_TIME_SECS);
+ if (sieve_setting_get_duration_value(svinst, "sieve_max_cpu_time",
+ &period)) {
+ if (period > (UINT_MAX / 1000))
+ svinst->max_cpu_time_secs = (UINT_MAX / 1000);
+ else
+ svinst->max_cpu_time_secs = (unsigned int)period;
+ }
+ svinst->resource_usage_timeout_secs =
+ SIEVE_DEFAULT_RESOURCE_USAGE_TIMEOUT_SECS;
+ if (sieve_setting_get_duration_value(
+ svinst, "sieve_resource_usage_timeout", &period)) {
+ if (period > UINT_MAX)
+ svinst->resource_usage_timeout_secs = UINT_MAX;
+ else {
+ svinst->resource_usage_timeout_secs =
+ (unsigned int)period;
+ }
+ }
+
+ (void)sieve_address_source_parse_from_setting(
+ svinst, svinst->pool, "sieve_redirect_envelope_from",
+ &svinst->redirect_from);
+
+ svinst->redirect_duplicate_period = DEFAULT_REDIRECT_DUPLICATE_PERIOD;
+ if (sieve_setting_get_duration_value(
+ svinst, "sieve_redirect_duplicate_period", &period)) {
+ if (period > UINT_MAX)
+ svinst->redirect_duplicate_period = UINT_MAX;
+ else {
+ svinst->redirect_duplicate_period =
+ (unsigned int)period;
+ }
+ }
+
+ str_setting = sieve_setting_get(svinst, "sieve_user_email");
+ if (str_setting != NULL && *str_setting != '\0') {
+ struct smtp_address *address;
+ if (smtp_address_parse_path(
+ svinst->pool, str_setting,
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL,
+ &address, &error) < 0) {
+ e_warning(svinst->event,
+ "Invalid address value for setting "
+ "`sieve_user_email': %s", error);
+ } else {
+ svinst->user_email = address;
+ }
+ }
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-settings.h b/pigeonhole/src/lib-sieve/sieve-settings.h
new file mode 100644
index 0000000..f2dfca2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-settings.h
@@ -0,0 +1,57 @@
+#ifndef SIEVE_SETTINGS_H
+#define SIEVE_SETTINGS_H
+
+#include "sieve-common.h"
+
+/*
+ * Access to settings
+ */
+
+static inline const char *
+sieve_setting_get(struct sieve_instance *svinst, const char *identifier)
+{
+ const struct sieve_callbacks *callbacks = svinst->callbacks;
+
+ if (callbacks == NULL || callbacks->get_setting == NULL)
+ return NULL;
+
+ return callbacks->get_setting(svinst->context, identifier);
+}
+
+bool sieve_setting_get_uint_value(struct sieve_instance *svinst,
+ const char *setting,
+ unsigned long long int *value_r);
+bool sieve_setting_get_int_value(struct sieve_instance *svinst,
+ const char *setting, long long int *value_r);
+bool sieve_setting_get_size_value(struct sieve_instance *svinst,
+ const char *setting, size_t *value_r);
+bool sieve_setting_get_bool_value(struct sieve_instance *svinst,
+ const char *setting, bool *value_r);
+bool sieve_setting_get_duration_value(struct sieve_instance *svinst,
+ const char *setting,
+ sieve_number_t *value_r);
+
+/*
+ * Main Sieve engine settings
+ */
+
+void sieve_settings_load(struct sieve_instance *svinst);
+
+/*
+ * Home directory
+ */
+
+static inline const char *
+sieve_environment_get_homedir(struct sieve_instance *svinst)
+{
+ const struct sieve_callbacks *callbacks = svinst->callbacks;
+
+ if (svinst->home_dir != NULL)
+ return svinst->home_dir;
+ if (callbacks == NULL || callbacks->get_homedir == NULL)
+ return NULL;
+
+ return callbacks->get_homedir(svinst->context);
+}
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-smtp.c b/pigeonhole/src/lib-sieve/sieve-smtp.c
new file mode 100644
index 0000000..40b40ea
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-smtp.c
@@ -0,0 +1,95 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+#include "lib.h"
+#include "smtp-address.h"
+
+#include "sieve-common.h"
+#include "sieve-smtp.h"
+
+struct sieve_smtp_context {
+ const struct sieve_script_env *senv;
+ void *handle;
+
+ bool sent:1;
+};
+
+bool sieve_smtp_available
+(const struct sieve_script_env *senv)
+{
+ return ( senv->smtp_start != NULL && senv->smtp_add_rcpt != NULL &&
+ senv->smtp_send != NULL && senv->smtp_finish != NULL );
+}
+
+struct sieve_smtp_context *sieve_smtp_start
+(const struct sieve_script_env *senv,
+ const struct smtp_address *mail_from)
+{
+ struct sieve_smtp_context *sctx;
+ void *handle;
+
+ if ( !sieve_smtp_available(senv) )
+ return NULL;
+
+ handle = senv->smtp_start(senv, mail_from);
+ i_assert( handle != NULL );
+
+ sctx = i_new(struct sieve_smtp_context, 1);
+ sctx->senv = senv;
+ sctx->handle = handle;
+
+ return sctx;
+}
+
+void sieve_smtp_add_rcpt
+(struct sieve_smtp_context *sctx,
+ const struct smtp_address *rcpt_to)
+{
+ i_assert(!sctx->sent);
+ sctx->senv->smtp_add_rcpt(sctx->senv, sctx->handle, rcpt_to);
+}
+
+struct ostream *sieve_smtp_send
+(struct sieve_smtp_context *sctx)
+{
+ i_assert(!sctx->sent);
+ sctx->sent = TRUE;
+
+ return sctx->senv->smtp_send(sctx->senv, sctx->handle);
+}
+
+struct sieve_smtp_context *sieve_smtp_start_single
+(const struct sieve_script_env *senv,
+ const struct smtp_address *rcpt_to,
+ const struct smtp_address *mail_from,
+ struct ostream **output_r)
+{
+ struct sieve_smtp_context *sctx;
+
+ sctx = sieve_smtp_start(senv, mail_from);
+ sieve_smtp_add_rcpt(sctx, rcpt_to);
+ *output_r = sieve_smtp_send(sctx);
+
+ return sctx;
+}
+
+void sieve_smtp_abort
+(struct sieve_smtp_context *sctx)
+{
+ const struct sieve_script_env *senv = sctx->senv;
+ void *handle = sctx->handle;
+
+ i_free(sctx);
+ i_assert(senv->smtp_abort != NULL);
+ senv->smtp_abort(senv, handle);
+}
+
+int sieve_smtp_finish
+(struct sieve_smtp_context *sctx, const char **error_r)
+{
+ const struct sieve_script_env *senv = sctx->senv;
+ void *handle = sctx->handle;
+
+ i_free(sctx);
+ return senv->smtp_finish(senv, handle, error_r);
+}
+
diff --git a/pigeonhole/src/lib-sieve/sieve-smtp.h b/pigeonhole/src/lib-sieve/sieve-smtp.h
new file mode 100644
index 0000000..f7b9c84
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-smtp.h
@@ -0,0 +1,31 @@
+#ifndef SIEVE_SMTP_H
+#define SIEVE_SMTP_H
+
+#include "sieve-common.h"
+
+bool sieve_smtp_available
+ (const struct sieve_script_env *senv);
+
+struct sieve_smtp_context;
+
+struct sieve_smtp_context *sieve_smtp_start
+ (const struct sieve_script_env *senv,
+ const struct smtp_address *mail_from);
+void sieve_smtp_add_rcpt
+ (struct sieve_smtp_context *sctx,
+ const struct smtp_address *rcpt_to);
+struct ostream *sieve_smtp_send
+ (struct sieve_smtp_context *sctx);
+
+struct sieve_smtp_context *sieve_smtp_start_single
+ (const struct sieve_script_env *senv,
+ const struct smtp_address *rcpt_to,
+ const struct smtp_address *mail_from,
+ struct ostream **output_r);
+
+void sieve_smtp_abort
+ (struct sieve_smtp_context *sctx);
+int sieve_smtp_finish
+ (struct sieve_smtp_context *sctx, const char **error_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-storage-private.h b/pigeonhole/src/lib-sieve/sieve-storage-private.h
new file mode 100644
index 0000000..16af3d4
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-storage-private.h
@@ -0,0 +1,256 @@
+#ifndef SIEVE_STORAGE_PRIVATE_H
+#define SIEVE_STORAGE_PRIVATE_H
+
+#include "sieve.h"
+#include "sieve-error-private.h"
+
+#include "sieve-storage.h"
+
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER"sieve/"
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES \
+ MAILBOX_ATTRIBUTE_PREFIX_SIEVE"files/"
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT \
+ MAILBOX_ATTRIBUTE_PREFIX_SIEVE"default"
+
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK 'L'
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT 'S'
+
+struct sieve_storage;
+
+ARRAY_DEFINE_TYPE(sieve_storage_class, const struct sieve_storage *);
+
+struct sieve_storage_vfuncs {
+ struct sieve_storage *(*alloc)(void);
+ void (*destroy)(struct sieve_storage *storage);
+ int (*init)(struct sieve_storage *storage, const char *const *options,
+ enum sieve_error *error_r);
+
+ int (*get_last_change)(struct sieve_storage *storage,
+ time_t *last_change_r);
+ void (*set_modified)(struct sieve_storage *storage, time_t mtime);
+
+ int (*is_singular)(struct sieve_storage *storage);
+
+ /* script access */
+ struct sieve_script *(*get_script)(struct sieve_storage *storage,
+ const char *name);
+
+ /* script sequence */
+ struct sieve_script_sequence *(*get_script_sequence)(
+ struct sieve_storage *storage, enum sieve_error *error_r);
+ struct sieve_script *(*script_sequence_next)(
+ struct sieve_script_sequence *seq, enum sieve_error *error_r);
+ void (*script_sequence_destroy)(struct sieve_script_sequence *seq);
+
+ /* active script */
+ int (*active_script_get_name)(struct sieve_storage *storage,
+ const char **name_r);
+ struct sieve_script *(*active_script_open)(
+ struct sieve_storage *storage);
+ int (*deactivate)(struct sieve_storage *storage);
+ int (*active_script_get_last_change)(struct sieve_storage *storage,
+ time_t *last_change_r);
+
+ /* listing scripts */
+ struct sieve_storage_list_context *(*list_init)(
+ struct sieve_storage *storage);
+ const char *(*list_next)(struct sieve_storage_list_context *lctx,
+ bool *active_r);
+ int (*list_deinit)(struct sieve_storage_list_context *lctx);
+
+ /* saving scripts */
+ // FIXME: simplify this API; reduce this mostly to a single save function
+ struct sieve_storage_save_context *(*save_alloc)(
+ struct sieve_storage *storage);
+ int (*save_init)(struct sieve_storage_save_context *sctx,
+ const char *scriptname, struct istream *input);
+ int (*save_continue)(struct sieve_storage_save_context *sctx);
+ int (*save_finish)(struct sieve_storage_save_context *sctx);
+ struct sieve_script *(*save_get_tempscript)(
+ struct sieve_storage_save_context *sctx);
+ void (*save_cancel)(struct sieve_storage_save_context *sctx);
+ int (*save_commit)(struct sieve_storage_save_context *sctx);
+ int (*save_as)(struct sieve_storage *storage, struct istream *input,
+ const char *name);
+ int (*save_as_active)(struct sieve_storage *storage,
+ struct istream *input, time_t mtime);
+
+ /* checking quota */
+ int (*quota_havespace)(struct sieve_storage *storage,
+ const char *scriptname, size_t size,
+ enum sieve_storage_quota *quota_r,
+ uint64_t *limit_r);
+};
+
+struct sieve_storage {
+ pool_t pool;
+ unsigned int refcount;
+ struct sieve_instance *svinst;
+ struct event *event;
+
+ const char *driver_name;
+ unsigned int version;
+
+ const struct sieve_storage *storage_class;
+ struct sieve_storage_vfuncs v;
+
+ uint64_t max_scripts;
+ uint64_t max_storage;
+
+ char *error;
+ enum sieve_error error_code;
+
+ const char *data;
+ const char *location;
+ const char *script_name;
+ const char *bin_dir;
+
+ const char *default_name;
+ const char *default_location;
+ struct sieve_storage *default_for;
+
+ struct mail_namespace *sync_inbox_ns;
+
+ enum sieve_storage_flags flags;
+
+ /* this is the main personal storage */
+ bool main_storage:1;
+ bool allows_synchronization:1;
+ bool is_default:1;
+};
+
+struct event *
+sieve_storage_event_create(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class);
+struct sieve_storage *
+sieve_storage_alloc(struct sieve_instance *svinst, struct event *event,
+ const struct sieve_storage *storage_class, const char *data,
+ enum sieve_storage_flags flags, bool main) ATTR_NULL(2, 4);
+
+int sieve_storage_setup_bindir(struct sieve_storage *storage, mode_t mode);
+
+/*
+ * Active script
+ */
+
+int sieve_storage_active_script_is_default(struct sieve_storage *storage);
+
+/*
+ * Listing scripts
+ */
+
+struct sieve_storage_list_context {
+ struct sieve_storage *storage;
+
+ bool seen_active:1; // Just present for assertions
+ bool seen_default:1;
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence {
+ struct sieve_storage *storage;
+};
+
+/*
+ * Saving scripts
+ */
+
+struct sieve_storage_save_context {
+ pool_t pool;
+ struct sieve_storage *storage;
+ struct event *event;
+
+ const char *scriptname, *active_scriptname;
+ struct sieve_script *scriptobject;
+
+ struct istream *input;
+
+ time_t mtime;
+
+ bool failed:1;
+ bool finished:1;
+};
+
+/*
+ * Storage class
+ */
+
+struct sieve_storage_class_registry;
+
+void sieve_storages_init(struct sieve_instance *svinst);
+void sieve_storages_deinit(struct sieve_instance *svinst);
+
+void sieve_storage_class_register(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class);
+void sieve_storage_class_unregister(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class);
+const struct sieve_storage *
+sieve_storage_find_class(struct sieve_instance *svinst, const char *name);
+
+/*
+ * Built-in storage drivers
+ */
+
+/* data (currently only for internal use) */
+
+#define SIEVE_DATA_STORAGE_DRIVER_NAME "data"
+
+extern const struct sieve_storage sieve_data_storage;
+
+/* file */
+
+#define SIEVE_FILE_STORAGE_DRIVER_NAME "file"
+
+extern const struct sieve_storage sieve_file_storage;
+
+struct sieve_storage *
+sieve_file_storage_init_legacy(struct sieve_instance *svinst,
+ const char *active_path,
+ const char *storage_path,
+ enum sieve_storage_flags flags,
+ enum sieve_error *error_r) ATTR_NULL(6);
+
+/* dict */
+
+#define SIEVE_DICT_STORAGE_DRIVER_NAME "dict"
+
+extern const struct sieve_storage sieve_dict_storage;
+
+/* ldap */
+
+#define SIEVE_LDAP_STORAGE_DRIVER_NAME "ldap"
+
+extern const struct sieve_storage sieve_ldap_storage;
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_set_internal_error(struct sieve_storage *storage);
+
+void sieve_storage_copy_error(struct sieve_storage *storage,
+ const struct sieve_storage *source);
+
+/*
+ * Synchronization
+ */
+
+int sieve_storage_sync_init(struct sieve_storage *storage,
+ struct mail_user *user);
+void sieve_storage_sync_deinit(struct sieve_storage *storage);
+
+int sieve_storage_sync_script_save(struct sieve_storage *storage,
+ const char *name);
+int sieve_storage_sync_script_rename(struct sieve_storage *storage,
+ const char *oldname, const char *newname);
+int sieve_storage_sync_script_delete(struct sieve_storage *storage,
+ const char *name);
+
+int sieve_storage_sync_script_activate(struct sieve_storage *storage);
+int sieve_storage_sync_deactivate(struct sieve_storage *storage);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-storage-sync.c b/pigeonhole/src/lib-sieve/sieve-storage-sync.c
new file mode 100644
index 0000000..b1abfcc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-storage-sync.c
@@ -0,0 +1,195 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "ioloop.h"
+#include "mail-storage-private.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+/*
+ * Synchronization
+ */
+
+int sieve_storage_sync_init
+(struct sieve_storage *storage, struct mail_user *user)
+{
+ enum sieve_storage_flags sflags = storage->flags;
+
+ if ( (sflags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ (sflags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+ return 0;
+
+ if ( !storage->allows_synchronization ) {
+ if ( (sflags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 )
+ return -1;
+ return 0;
+ }
+
+ e_debug(storage->event, "sync: Synchronization active");
+
+ storage->sync_inbox_ns = mail_namespace_find_inbox(user->namespaces);
+ return 0;
+}
+
+void sieve_storage_sync_deinit
+(struct sieve_storage *storage ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+/*
+ * Sync attributes
+ */
+
+static int sieve_storage_sync_transaction_begin
+(struct sieve_storage *storage, struct mailbox_transaction_context **trans_r)
+{
+ enum mailbox_flags mflags = MAILBOX_FLAG_IGNORE_ACLS;
+ struct mail_namespace *ns = storage->sync_inbox_ns;
+ struct mailbox *inbox;
+ enum mail_error error;
+
+ if (ns == NULL)
+ return 0;
+
+ inbox = mailbox_alloc(ns->list, "INBOX", mflags);
+ if (mailbox_open(inbox) < 0) {
+ e_warning(storage->event, "sync: "
+ "Failed to open user INBOX for attribute modifications: %s",
+ mailbox_get_last_internal_error(inbox, &error));
+ mailbox_free(&inbox);
+ return -1;
+ }
+
+ *trans_r = mailbox_transaction_begin(inbox,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL,
+ __func__);
+ return 1;
+}
+
+static int sieve_storage_sync_transaction_finish
+(struct sieve_storage *storage, struct mailbox_transaction_context **trans)
+{
+ struct mailbox *inbox;
+ int ret;
+
+ inbox = mailbox_transaction_get_mailbox(*trans);
+
+ if ((ret=mailbox_transaction_commit(trans)) < 0) {
+ enum mail_error error;
+
+ e_warning(storage->event, "sync: "
+ "Failed to update INBOX attributes: %s",
+ mail_storage_get_last_error(
+ mailbox_get_storage(inbox), &error));
+ }
+
+ mailbox_free(&inbox);
+ return ret;
+}
+
+int sieve_storage_sync_script_save
+(struct sieve_storage *storage, const char *name)
+{
+ struct mailbox_transaction_context *trans;
+ const char *key;
+ int ret;
+
+ if ((ret=sieve_storage_sync_transaction_begin
+ (storage, &trans)) <= 0)
+ return ret;
+
+ key = t_strconcat
+ (MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
+
+ mail_index_attribute_set(trans->itrans, TRUE, key, ioloop_time, 0);
+
+ return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_rename
+(struct sieve_storage *storage, const char *oldname,
+ const char *newname)
+{
+ struct mailbox_transaction_context *trans;
+ const char *oldkey, *newkey;
+ int ret;
+
+ if ((ret=sieve_storage_sync_transaction_begin
+ (storage, &trans)) <= 0)
+ return ret;
+
+ oldkey = t_strconcat
+ (MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, oldname, NULL);
+ newkey = t_strconcat
+ (MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, newname, NULL);
+
+ mail_index_attribute_unset(trans->itrans, TRUE, oldkey, ioloop_time);
+ mail_index_attribute_set(trans->itrans, TRUE, newkey, ioloop_time, 0);
+
+ return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_delete
+(struct sieve_storage *storage, const char *name)
+{
+ struct mailbox_transaction_context *trans;
+ const char *key;
+ int ret;
+
+ if ((ret=sieve_storage_sync_transaction_begin
+ (storage, &trans)) <= 0)
+ return ret;
+
+ key = t_strconcat
+ (MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES, name, NULL);
+
+ mail_index_attribute_unset(trans->itrans, TRUE, key, ioloop_time);
+
+ return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_script_activate
+(struct sieve_storage *storage)
+{
+ struct mailbox_transaction_context *trans;
+ int ret;
+
+ if ((ret=sieve_storage_sync_transaction_begin
+ (storage, &trans)) <= 0)
+ return ret;
+
+ mail_index_attribute_set(trans->itrans,
+ TRUE, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT, ioloop_time, 0);
+
+ return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+int sieve_storage_sync_deactivate
+(struct sieve_storage *storage)
+{
+ struct mailbox_transaction_context *trans;
+ int ret;
+
+ if ((ret=sieve_storage_sync_transaction_begin
+ (storage, &trans)) <= 0)
+ return ret;
+
+ mail_index_attribute_unset(trans->itrans,
+ TRUE, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT, ioloop_time);
+
+ return sieve_storage_sync_transaction_finish(storage, &trans);
+}
+
+
diff --git a/pigeonhole/src/lib-sieve/sieve-storage.c b/pigeonhole/src/lib-sieve/sieve-storage.c
new file mode 100644
index 0000000..1e2aa34
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-storage.c
@@ -0,0 +1,1590 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str-sanitize.h"
+#include "home-expand.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "ioloop.h"
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <utime.h>
+
+#define CRITICAL_MSG \
+ "Internal error occurred. Refer to server log for more information."
+#define CRITICAL_MSG_STAMP CRITICAL_MSG " [%Y-%m-%d %H:%M:%S]"
+
+struct event_category event_category_sieve_storage = {
+ .parent = &event_category_sieve,
+ .name = "sieve-storage",
+};
+
+/*
+ * Storage class
+ */
+
+struct sieve_storage_class_registry {
+ ARRAY_TYPE(sieve_storage_class) storage_classes;
+};
+
+void sieve_storages_init(struct sieve_instance *svinst)
+{
+ svinst->storage_reg = p_new(svinst->pool,
+ struct sieve_storage_class_registry, 1);
+ p_array_init(&svinst->storage_reg->storage_classes, svinst->pool, 8);
+
+ sieve_storage_class_register(svinst, &sieve_file_storage);
+ sieve_storage_class_register(svinst, &sieve_dict_storage);
+ sieve_storage_class_register(svinst, &sieve_ldap_storage);
+}
+
+void sieve_storages_deinit(struct sieve_instance *svinst ATTR_UNUSED)
+{
+ /* nothing yet */
+}
+
+void sieve_storage_class_register(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class)
+{
+ struct sieve_storage_class_registry *reg = svinst->storage_reg;
+ const struct sieve_storage *old_class;
+
+ old_class = sieve_storage_find_class(svinst,
+ storage_class->driver_name);
+ if (old_class != NULL) {
+ if (old_class->v.alloc == NULL) {
+ /* replacing a "support not compiled in" storage class
+ */
+ sieve_storage_class_unregister(svinst, old_class);
+ } else {
+ i_panic("sieve_storage_class_register(%s): "
+ "Already registered",
+ storage_class->driver_name);
+ }
+ }
+
+ array_append(&reg->storage_classes, &storage_class, 1);
+}
+
+void sieve_storage_class_unregister(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class)
+{
+ struct sieve_storage_class_registry *reg = svinst->storage_reg;
+ const struct sieve_storage *const *classes;
+ unsigned int i, count;
+
+ classes = array_get(&reg->storage_classes, &count);
+ for (i = 0; i < count; i++) {
+ if (classes[i] == storage_class) {
+ array_delete(&reg->storage_classes, i, 1);
+ break;
+ }
+ }
+}
+
+const struct sieve_storage *
+sieve_storage_find_class(struct sieve_instance *svinst, const char *name)
+{
+ struct sieve_storage_class_registry *reg = svinst->storage_reg;
+ const struct sieve_storage *const *classes;
+ unsigned int i, count;
+
+ i_assert(name != NULL);
+
+ classes = array_get(&reg->storage_classes, &count);
+ for (i = 0; i < count; i++) {
+ if (strcasecmp(classes[i]->driver_name, name) == 0)
+ return classes[i];
+ }
+ return NULL;
+}
+
+/*
+ * Storage instance
+ */
+
+static const char *split_next_arg(const char *const **_args)
+{
+ const char *const *args = *_args;
+ const char *str = args[0];
+
+ /* join arguments for escaped ";" separator */
+
+ args++;
+ while (*args != NULL && **args == '\0') {
+ args++;
+ if (*args == NULL) {
+ /* string ends with ";", just ignore it. */
+ break;
+ }
+ str = t_strconcat(str, ";", *args, NULL);
+ args++;
+ }
+ *_args = args;
+ return str;
+}
+
+static int
+sieve_storage_driver_parse(struct sieve_instance *svinst, const char **data,
+ const struct sieve_storage **driver_r)
+{
+ const struct sieve_storage *storage_class = NULL;
+ const char *p;
+
+ p = strchr(*data, ':');
+ if (p == NULL)
+ return 0;
+
+ /* Lookup storage driver */
+ T_BEGIN {
+ const char *driver;
+
+ driver = t_strdup_until(*data, p);
+ *data = p+1;
+
+ storage_class = sieve_storage_find_class(svinst, driver);
+ if (storage_class == NULL) {
+ e_error(svinst->event,
+ "Unknown storage driver module `%s'",
+ driver);
+ } else if (storage_class->v.alloc == NULL) {
+ e_error(svinst->event,
+ "Support not compiled in for storage driver `%s'",
+ driver);
+ storage_class = NULL;
+ }
+ } T_END;
+
+ *driver_r = storage_class;
+ return (storage_class == NULL ? -1 : 1);
+}
+
+static int
+sieve_storage_data_parse(struct sieve_storage *storage, const char *data,
+ const char **location_r, const char *const **options_r)
+{
+ ARRAY_TYPE(const_string) options;
+ const char *const *tmp;
+
+ if (*data == '\0') {
+ *options_r = NULL;
+ *location_r = data;
+ return 0;
+ }
+
+ /* <location> */
+ tmp = t_strsplit(data, ";");
+ *location_r = split_next_arg(&tmp);
+
+ if (options_r != NULL) {
+ t_array_init(&options, 8);
+
+ /* [<option> *(';' <option>)] */
+ while (*tmp != NULL) {
+ const char *option = split_next_arg(&tmp);
+
+ if (strncasecmp(option, "name=", 5) == 0) {
+ if (option[5] == '\0') {
+ e_error(storage->event,
+ "Failed to parse storage location: "
+ "Empty name not allowed");
+ return -1;
+ }
+
+ if (storage->script_name == NULL) {
+ if (!sieve_script_name_is_valid(option+5)) {
+ e_error(storage->event,
+ "Failed to parse storage location: "
+ "Invalid script name `%s'.",
+ str_sanitize(option+5, 80));
+ return -1;
+ }
+ storage->script_name = p_strdup(storage->pool, option+5);
+ }
+
+ } else if (strncasecmp(option, "bindir=", 7) == 0) {
+ const char *bin_dir = option+7;
+
+ if (bin_dir[0] == '\0') {
+ e_error(storage->event,
+ "Failed to parse storage location: "
+ "Empty bindir not allowed");
+ return -1;
+ }
+
+ if (bin_dir[0] == '~') {
+ /* home-relative path. change to absolute. */
+ const char *home = sieve_environment_get_homedir(storage->svinst);
+
+ if (home != NULL) {
+ bin_dir = home_expand_tilde(bin_dir, home);
+ } else if (bin_dir[1] == '/' || bin_dir[1] == '\0') {
+ e_error(storage->event,
+ "Failed to parse storage location: "
+ "bindir is relative to home directory (~/), "
+ "but home directory cannot be determined");
+ return -1;
+ }
+ }
+
+ storage->bin_dir = p_strdup(storage->pool, bin_dir);
+ } else {
+ array_append(&options, &option, 1);
+ }
+ }
+
+ (void)array_append_space(&options);
+ *options_r = array_idx(&options, 0);
+ }
+
+ return 0;
+}
+
+struct event *
+sieve_storage_event_create(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class)
+{
+ struct event *event;
+
+ event = event_create(svinst->event);
+ event_add_category(event, &event_category_sieve_storage);
+ event_add_str(event, "driver", storage_class->driver_name);
+ event_set_append_log_prefix(
+ event, t_strdup_printf("%s storage: ",
+ storage_class->driver_name));
+
+ return event;
+}
+
+struct sieve_storage *
+sieve_storage_alloc(struct sieve_instance *svinst, struct event *event,
+ const struct sieve_storage *storage_class, const char *data,
+ enum sieve_storage_flags flags, bool main)
+{
+ struct sieve_storage *storage;
+
+ i_assert(storage_class->v.alloc != NULL);
+ storage = storage_class->v.alloc();
+
+ storage->storage_class = storage_class;
+ storage->refcount = 1;
+ storage->svinst = svinst;
+ storage->flags = flags;
+ storage->data = p_strdup_empty(storage->pool, data);
+ storage->main_storage = main;
+
+ if (event != NULL) {
+ storage->event = event;
+ event_ref(storage->event);
+ } else {
+ storage->event =
+ sieve_storage_event_create(svinst, storage_class);
+ }
+
+ return storage;
+}
+
+static struct sieve_storage *
+sieve_storage_init(struct sieve_instance *svinst,
+ const struct sieve_storage *storage_class, const char *data,
+ enum sieve_storage_flags flags, bool main,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ const char *const *options;
+ const char *location;
+ struct event *event;
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ i_assert(storage_class->v.init != NULL);
+
+ event = sieve_storage_event_create(svinst, storage_class);
+
+ if ((flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 &&
+ !storage_class->allows_synchronization) {
+ e_debug(event, "Storage does not support synchronization");
+ *error_r = SIEVE_ERROR_NOT_POSSIBLE;
+ event_unref(&event);
+ return NULL;
+ }
+
+ if ((flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 &&
+ storage_class->v.save_init == NULL) {
+ e_error(event, "Storage does not support write access");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ event_unref(&event);
+ return NULL;
+ }
+
+ T_BEGIN {
+ storage = sieve_storage_alloc(svinst, event, storage_class,
+ data, flags, main);
+
+ if (sieve_storage_data_parse(storage, data,
+ &location, &options) < 0) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ sieve_storage_unref(&storage);
+ storage = NULL;
+ } else {
+ storage->location = p_strdup(storage->pool, location);
+
+ event_add_str(event, "script_location",
+ storage->location);
+
+ if (storage_class->v.init(storage, options,
+ error_r) < 0) {
+ sieve_storage_unref(&storage);
+ storage = NULL;
+ }
+ }
+ } T_END;
+
+ event_unref(&event);
+ return storage;
+}
+
+struct sieve_storage *
+sieve_storage_create(struct sieve_instance *svinst, const char *location,
+ enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+ const struct sieve_storage *storage_class;
+ enum sieve_error error;
+ const char *data;
+ int ret;
+
+ /* Dont use this function for creating a synchronizing storage */
+ i_assert((flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0);
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ data = location;
+ if ((ret = sieve_storage_driver_parse(svinst, &data,
+ &storage_class)) < 0) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return NULL;
+ }
+
+ if (ret == 0)
+ storage_class = &sieve_file_storage;
+
+ return sieve_storage_init(svinst, storage_class, data, flags,
+ FALSE, error_r);
+}
+
+static struct sieve_storage *
+sieve_storage_do_create_main(struct sieve_instance *svinst,
+ struct mail_user *user,
+ enum sieve_storage_flags flags,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = NULL;
+ const struct sieve_storage
+ *sieve_class = NULL,
+ *sieve_dir_class = NULL;
+ const char *set_sieve, *set_sieve_dir;
+ const char *data, *storage_path;
+ unsigned long long int uint_setting;
+ size_t size_setting;
+ int ret;
+
+ /* Sieve storage location */
+
+ set_sieve = sieve_setting_get(svinst, "sieve");
+
+ if (set_sieve != NULL) {
+ if (*set_sieve == '\0') {
+ /* disabled */
+ e_debug(svinst->event, "storage: "
+ "Personal storage is disabled (sieve=\"\")");
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ return NULL;
+ }
+
+ data = set_sieve;
+ if ((ret = sieve_storage_driver_parse(svinst, &data,
+ &sieve_class)) < 0) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return NULL;
+ }
+
+ if (ret > 0) {
+ /* The normal case: explicit driver name */
+ storage = sieve_storage_init(svinst, sieve_class, data,
+ flags, TRUE, error_r);
+ if (storage == NULL)
+ return NULL;
+ }
+
+ /* No driver name */
+ }
+
+ if (storage == NULL) {
+ /* Script storage directory configuration (deprecated) */
+
+ set_sieve_dir = sieve_setting_get(svinst, "sieve_dir");
+ if (set_sieve_dir == NULL) {
+ set_sieve_dir = sieve_setting_get(svinst,
+ "sieve_storage");
+ }
+
+ if (set_sieve_dir == NULL || *set_sieve_dir == '\0') {
+ storage_path = "";
+ } else {
+ const char *p;
+
+ /* Parse and check driver */
+ storage_path = set_sieve_dir;
+ if ((ret = sieve_storage_driver_parse(
+ svinst, &storage_path, &sieve_dir_class)) < 0) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return NULL;
+ }
+
+ if (ret > 0 && sieve_dir_class != &sieve_file_storage) {
+ e_error(svinst->event, "storage: "
+ "Cannot use deprecated sieve_dir= setting "
+ "with `%s' driver for main Sieve storage",
+ sieve_dir_class->driver_name);
+ }
+
+ /* Ignore any options */
+ p = strchr(storage_path, ';');
+ if (p != NULL)
+ storage_path = t_strdup_until(storage_path, p);
+ }
+
+ storage = sieve_file_storage_init_legacy(svinst, set_sieve,
+ storage_path, flags,
+ error_r);
+ }
+
+ if (storage == NULL)
+ return NULL;
+
+ (void)sieve_storage_sync_init(storage, user);
+
+ /* Get quota settings if storage driver provides none */
+
+ if (storage->max_storage == 0 &&
+ sieve_setting_get_size_value(svinst, "sieve_quota_max_storage",
+ &size_setting)) {
+ storage->max_storage = size_setting;
+ }
+
+ if (storage->max_scripts == 0 &&
+ sieve_setting_get_uint_value(svinst, "sieve_quota_max_scripts",
+ &uint_setting)) {
+ storage->max_scripts = uint_setting;
+ }
+
+ if (storage->max_storage > 0) {
+ e_debug(storage->event, "quota: "
+ "Storage limit: %llu bytes",
+ (unsigned long long int) storage->max_storage);
+ }
+ if (storage->max_scripts > 0) {
+ e_debug(storage->event, "quota: "
+ "Script count limit: %llu scripts",
+ (unsigned long long int) storage->max_scripts);
+ }
+ return storage;
+}
+
+struct sieve_storage *
+sieve_storage_create_main(struct sieve_instance *svinst, struct mail_user *user,
+ enum sieve_storage_flags flags,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ const char *set_enabled, *set_default, *set_default_name;
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ /* Check whether Sieve is disabled for this user */
+ if ((set_enabled = sieve_setting_get(svinst, "sieve_enabled")) != NULL &&
+ strcasecmp(set_enabled, "no") == 0) {
+ e_debug(svinst->event,
+ "Sieve is disabled for this user");
+ *error_r = SIEVE_ERROR_NOT_POSSIBLE;
+ return NULL;
+ }
+
+ /* Determine location for default script */
+ set_default = sieve_setting_get(svinst, "sieve_default");
+ if (set_default == NULL) {
+ /* For backwards compatibility */
+ set_default = sieve_setting_get(svinst, "sieve_global_path");
+ }
+
+ /* Attempt to locate user's main storage */
+ storage = sieve_storage_do_create_main(svinst, user, flags, error_r);
+ if (storage != NULL) {
+ /* Success; record default script location for later use */
+ storage->default_location =
+ p_strdup_empty(storage->pool, set_default);
+
+ set_default_name =
+ sieve_setting_get(svinst, "sieve_default_name");
+ if (set_default_name != NULL && *set_default_name != '\0' &&
+ !sieve_script_name_is_valid(set_default_name)) {
+ e_error(storage->event,
+ "Invalid script name `%s' for `sieve_default_name' setting.",
+ str_sanitize(set_default_name, 80));
+ set_default_name = NULL;
+ }
+ storage->default_name =
+ p_strdup_empty(storage->pool, set_default_name);
+
+ if (storage->default_location != NULL &&
+ storage->default_name != NULL) {
+ e_debug(storage->event,
+ "Default script at `%s' is visible by name `%s'",
+ storage->default_location, storage->default_name);
+ }
+ } else if (*error_r != SIEVE_ERROR_TEMP_FAILURE &&
+ (flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ (flags & SIEVE_STORAGE_FLAG_READWRITE) == 0) {
+
+ /* Failed; try using default script location
+ (not for temporary failures, read/write access, or dsync) */
+ if (set_default == NULL) {
+ e_debug(svinst->event, "storage: "
+ "No default script location configured");
+ } else {
+ e_debug(svinst->event, "storage: "
+ "Trying default script location `%s'",
+ set_default);
+
+ storage = sieve_storage_create(svinst, set_default, 0,
+ error_r);
+ if (storage == NULL) {
+ switch (*error_r) {
+ case SIEVE_ERROR_NOT_FOUND:
+ e_debug(svinst->event, "storage: "
+ "Default script location `%s' not found",
+ set_default);
+ break;
+ case SIEVE_ERROR_TEMP_FAILURE:
+ e_error(svinst->event, "storage: "
+ "Failed to access default script location `%s' "
+ "(temporary failure)",
+ set_default);
+ break;
+ default:
+ e_error(svinst->event, "storage: "
+ "Failed to access default script location `%s'",
+ set_default);
+ break;
+ }
+ }
+ }
+ if (storage != NULL)
+ storage->is_default = TRUE;
+ }
+ return storage;
+}
+
+void sieve_storage_ref(struct sieve_storage *storage)
+{
+ storage->refcount++;
+}
+
+void sieve_storage_unref(struct sieve_storage **_storage)
+{
+ struct sieve_storage *storage = *_storage;
+
+ i_assert(storage->refcount > 0);
+
+ if (--storage->refcount != 0)
+ return;
+
+ if (storage->default_for != NULL) {
+ i_assert(storage->is_default);
+ sieve_storage_unref(&storage->default_for);
+ }
+
+ sieve_storage_sync_deinit(storage);
+
+ if (storage->v.destroy != NULL)
+ storage->v.destroy(storage);
+
+ i_free(storage->error);
+ event_unref(&storage->event);
+ pool_unref(&storage->pool);
+ *_storage = NULL;
+}
+
+int sieve_storage_setup_bindir(struct sieve_storage *storage, mode_t mode)
+{
+ const char *bin_dir = storage->bin_dir;
+ struct stat st;
+
+ if (bin_dir == NULL)
+ return -1;
+
+ if (stat(bin_dir, &st) == 0)
+ return 0;
+
+ if (errno == EACCES) {
+ e_error(storage->event,
+ "Failed to setup directory for binaries: "
+ "%s", eacces_error_get("stat", bin_dir));
+ return -1;
+ } else if (errno != ENOENT) {
+ e_error(storage->event,
+ "Failed to setup directory for binaries: "
+ "stat(%s) failed: %m",
+ bin_dir);
+ return -1;
+ }
+
+ if (mkdir_parents(bin_dir, mode) == 0) {
+ e_debug(storage->event,
+ "Created directory for binaries: %s", bin_dir);
+ return 1;
+ }
+
+ switch (errno) {
+ case EEXIST:
+ return 0;
+ case ENOENT:
+ e_error(storage->event,
+ "Directory for binaries was deleted while it was being created");
+ break;
+ case EACCES:
+ e_error(storage->event,
+ "%s", eacces_error_get_creating("mkdir_parents_chgrp", bin_dir));
+ break;
+ default:
+ e_error(storage->event,
+ "mkdir_parents_chgrp(%s) failed: %m", bin_dir);
+ break;
+ }
+
+ return -1;
+}
+
+int sieve_storage_is_singular(struct sieve_storage *storage)
+{
+ if (storage->v.is_singular == NULL)
+ return 1;
+ return storage->v.is_singular(storage);
+}
+
+int sieve_storage_get_last_change(struct sieve_storage *storage,
+ time_t *last_change_r)
+{
+ i_assert(storage->v.get_last_change != NULL);
+ return storage->v.get_last_change(storage, last_change_r);
+}
+
+void sieve_storage_set_modified(struct sieve_storage *storage, time_t mtime)
+{
+ if (storage->v.set_modified == NULL)
+ return;
+
+ storage->v.set_modified(storage, mtime);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *
+sieve_storage_get_script_direct(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ sieve_storage_clear_error(storage);
+
+ /* Validate script name */
+ if (name != NULL && !sieve_script_name_is_valid(name)) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_BAD_PARAMS,
+ "Invalid script name `%s'.",
+ str_sanitize(name, 80));
+ if (error_r != NULL)
+ *error_r = storage->error_code;
+ return NULL;
+ }
+
+ i_assert(storage->v.get_script != NULL);
+ script = storage->v.get_script(storage, name);
+ return script;
+}
+
+struct sieve_script *
+sieve_storage_get_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = storage->svinst;
+ struct sieve_script *script;
+
+ script = sieve_storage_get_script_direct(storage, name, error_r);
+ if (script == NULL) {
+ /* Error */
+ if (storage->error_code == SIEVE_ERROR_NOT_FOUND &&
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ storage->default_name != NULL &&
+ storage->default_location != NULL &&
+ strcmp(storage->default_name, name) == 0) {
+ /* Not found; if this name maps to the default script,
+ try to access that instead */
+ i_assert(*storage->default_location != '\0');
+
+ e_debug(storage->event,
+ "Trying default script instead");
+
+ script = sieve_script_create(
+ svinst, storage->default_location, NULL,
+ error_r);
+ if (script != NULL) {
+ script->storage->is_default = TRUE;
+ script->storage->default_for = storage;
+ sieve_storage_ref(storage);
+ }
+
+ } else if (error_r != NULL) {
+ *error_r = storage->error_code;
+ }
+ }
+ return script;
+}
+
+struct sieve_script *
+sieve_storage_open_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = storage->svinst;
+ struct sieve_script *script;
+
+ script = sieve_storage_get_script(storage, name, error_r);
+ if (script == NULL)
+ return NULL;
+
+ if (sieve_script_open(script, error_r) >= 0)
+ return script;
+
+ /* Error */
+ sieve_script_unref(&script);
+ script = NULL;
+
+ if (storage->error_code == SIEVE_ERROR_NOT_FOUND &&
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ storage->default_name != NULL &&
+ storage->default_location != NULL &&
+ strcmp(storage->default_name, name) == 0) {
+ /* Not found; if this name maps to the default script,
+ try to open that instead */
+ i_assert(*storage->default_location != '\0');
+
+ e_debug(storage->event, "Trying default script instead");
+
+ script = sieve_script_create_open(
+ svinst, storage->default_location, NULL, error_r);
+ if (script != NULL) {
+ script->storage->is_default = TRUE;
+ script->storage->default_for = storage;
+ sieve_storage_ref(storage);
+ }
+ }
+ return script;
+}
+
+static int
+sieve_storage_check_script_direct(struct sieve_storage *storage,
+ const char *name, enum sieve_error *error_r)
+ ATTR_NULL(3)
+{
+ struct sieve_script *script;
+ enum sieve_error error;
+ int ret;
+
+ if (error_r == NULL)
+ error_r = &error;
+
+ script = sieve_storage_get_script_direct(storage, name, error_r);
+ if (script == NULL)
+ return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+
+ ret = sieve_script_open(script, error_r);
+ sieve_script_unref(&script);
+ return (ret >= 0 ? 1 : (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1));
+}
+
+int sieve_storage_check_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+ enum sieve_error error;
+
+ if (error_r == NULL)
+ error_r = &error;
+
+ script = sieve_storage_open_script(storage, name, error_r);
+ if (script == NULL)
+ return (*error_r == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+
+ sieve_script_unref(&script);
+ return 1;
+}
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *
+sieve_storage_get_script_sequence(struct sieve_storage *storage,
+ enum sieve_error *error_r)
+{
+ enum sieve_error error;
+
+ if (error_r != NULL)
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ i_assert(storage->v.get_script_sequence != NULL);
+ return storage->v.get_script_sequence(storage, error_r);
+}
+
+/*
+ * Active script
+ */
+
+static int
+sieve_storage_active_script_do_get_name(struct sieve_storage *storage,
+ const char **name_r, bool *default_r)
+ ATTR_NULL(3)
+{
+ struct sieve_instance *svinst = storage->svinst;
+ enum sieve_error error;
+ int ret;
+
+ if (default_r != NULL)
+ *default_r = FALSE;
+
+ i_assert(storage->v.active_script_get_name != NULL);
+ ret = storage->v.active_script_get_name(storage, name_r);
+
+ if (ret != 0 ||
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 ||
+ storage->default_location == NULL ||
+ storage->default_name == NULL) {
+ return ret;
+ }
+
+ *name_r = storage->default_name;
+
+ ret = sieve_script_check(svinst, storage->default_location,
+ NULL, &error);
+ if (ret <= 0)
+ return ret;
+
+ if (default_r != NULL)
+ *default_r = TRUE;
+ return 1;
+}
+
+int sieve_storage_active_script_get_name(struct sieve_storage *storage,
+ const char **name_r)
+{
+ return sieve_storage_active_script_do_get_name(storage, name_r, NULL);
+}
+
+int sieve_storage_active_script_is_default(struct sieve_storage *storage)
+{
+ const char *name;
+ bool is_default = FALSE;
+ int ret;
+
+ ret = sieve_storage_active_script_do_get_name(storage, &name,
+ &is_default);
+ return (ret < 0 ? -1 : (is_default ? 1 : 0));
+}
+
+struct sieve_script *
+sieve_storage_active_script_open(struct sieve_storage *storage,
+ enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = storage->svinst;
+ struct sieve_script *script;
+
+ i_assert(storage->v.active_script_open != NULL);
+ script = storage->v.active_script_open(storage);
+
+ if (script != NULL ||
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) != 0 ||
+ storage->default_location == NULL) {
+ if (error_r != NULL)
+ *error_r = storage->error_code;
+ return script;
+ }
+
+ /* Try default script location */
+ script = sieve_script_create_open(svinst, storage->default_location,
+ NULL, error_r);
+ if (script != NULL) {
+ script->storage->is_default = TRUE;
+ script->storage->default_for = storage;
+ sieve_storage_ref(storage);
+ }
+ return script;
+}
+
+int sieve_storage_deactivate(struct sieve_storage *storage, time_t mtime)
+{
+ int ret;
+
+ i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0);
+
+ i_assert(storage->v.deactivate != NULL);
+ ret = storage->v.deactivate(storage);
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(storage->event)->
+ set_name("sieve_storage_deactivated");
+ e_debug(e->event(), "Storage activated");
+
+ sieve_storage_set_modified(storage, mtime);
+ (void)sieve_storage_sync_deactivate(storage);
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(storage->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_deactivated");
+ e_debug(e->event(), "Failed to deactivate storage: %s",
+ storage->error);
+ }
+
+ return ret;
+}
+
+int sieve_storage_active_script_get_last_change(struct sieve_storage *storage,
+ time_t *last_change_r)
+{
+ i_assert(storage->v.active_script_get_last_change != NULL);
+
+ return storage->v.active_script_get_last_change(storage, last_change_r);
+}
+
+/*
+ * Listing scripts
+ */
+
+struct sieve_storage_list_context *
+sieve_storage_list_init(struct sieve_storage *storage)
+{
+ struct sieve_storage_list_context *lctx;
+
+ i_assert(storage->v.list_init != NULL);
+ lctx = storage->v.list_init(storage);
+
+ if (lctx != NULL)
+ lctx->storage = storage;
+
+ return lctx;
+}
+
+const char *
+sieve_storage_list_next(struct sieve_storage_list_context *lctx, bool *active_r)
+{
+ struct sieve_storage *storage = lctx->storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *scriptname;
+ bool have_default, script_active = FALSE;
+
+ have_default = (storage->default_name != NULL &&
+ storage->default_location != NULL &&
+ (storage->flags &
+ SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0);
+
+ i_assert(storage->v.list_next != NULL);
+ scriptname = storage->v.list_next(lctx, &script_active);
+
+ i_assert(!script_active || !lctx->seen_active);
+ if (script_active)
+ lctx->seen_active = TRUE;
+
+ if (scriptname != NULL) {
+ /* Remember when we see that the storage has its own script for
+ default */
+ if (have_default &&
+ strcmp(scriptname, storage->default_name) == 0)
+ lctx->seen_default = TRUE;
+
+ } else if (have_default && !lctx->seen_default &&
+ sieve_script_check(svinst, storage->default_location,
+ NULL, NULL) > 0) {
+
+ /* Return default script at the end if it was not listed
+ thus far (storage backend has no script under default
+ name) */
+ scriptname = storage->default_name;
+ lctx->seen_default = TRUE;
+
+ /* Mark default as active if no normal script is active */
+ if (!lctx->seen_active) {
+ script_active = TRUE;
+ lctx->seen_active = TRUE;
+ }
+ }
+
+ if (active_r != NULL)
+ *active_r = script_active;
+ return scriptname;
+}
+
+int sieve_storage_list_deinit(struct sieve_storage_list_context **_lctx)
+{
+ struct sieve_storage_list_context *lctx = *_lctx;
+ struct sieve_storage *storage = lctx->storage;
+ int ret;
+
+ i_assert(storage->v.list_deinit != NULL);
+ ret = storage->v.list_deinit(lctx);
+
+ *_lctx = NULL;
+ return ret;
+}
+
+/*
+ * Saving scripts
+ */
+
+static struct event *
+sieve_storage_save_create_event(struct sieve_storage *storage,
+ const char *scriptname) ATTR_NULL(2)
+{
+ struct event *event;
+
+ event = event_create(storage->event);
+ event_add_str(event, "script_name", scriptname);
+ if (scriptname == NULL) {
+ event_set_append_log_prefix(event, "save: ");
+ } else {
+ event_set_append_log_prefix(
+ event, t_strdup_printf("script `%s': save: ",
+ scriptname));
+ }
+
+ return event;
+}
+
+static void sieve_storage_save_cleanup(struct sieve_storage_save_context *sctx)
+{
+ if (sctx->scriptobject != NULL)
+ sieve_script_unref(&sctx->scriptobject);
+}
+
+static void sieve_storage_save_deinit(struct sieve_storage_save_context **_sctx)
+{
+ struct sieve_storage_save_context *sctx = *_sctx;
+
+ *_sctx = NULL;
+ if (sctx == NULL)
+ return;
+
+ sieve_storage_save_cleanup(sctx);
+ event_unref(&sctx->event);
+ pool_unref(&sctx->pool);
+}
+
+struct sieve_storage_save_context *
+sieve_storage_save_init(struct sieve_storage *storage, const char *scriptname,
+ struct istream *input)
+{
+ struct sieve_storage_save_context *sctx;
+
+ if (scriptname != NULL) {
+ /* Validate script name */
+ if (!sieve_script_name_is_valid(scriptname)) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_BAD_PARAMS,
+ "Invalid Sieve script name `%s'.",
+ str_sanitize(scriptname, 80));
+ return NULL;
+ }
+ }
+
+ i_assert((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0);
+
+ i_assert(storage->v.save_alloc != NULL);
+ sctx = storage->v.save_alloc(storage);
+ sctx->storage = storage;
+
+ sctx->event = sieve_storage_save_create_event(storage, scriptname);
+
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ set_name("sieve_storage_save_started");
+ e_debug(e->event(), "Started saving script");
+
+ i_assert(storage->v.save_init != NULL);
+ if ((storage->v.save_init(sctx, scriptname, input)) < 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Failed to save script: %s",
+ storage->error);
+
+ sieve_storage_save_deinit(&sctx);
+ return NULL;
+ }
+
+ sctx->mtime = (time_t)-1;
+
+ i_assert(sctx->input != NULL);
+
+ return sctx;
+}
+
+int sieve_storage_save_continue(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_storage *storage = sctx->storage;
+ int ret;
+
+ i_assert(storage->v.save_continue != NULL);
+ ret = storage->v.save_continue(sctx);
+ if (ret < 0)
+ sctx->failed = TRUE;
+ return ret;
+}
+
+int sieve_storage_save_finish(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_storage *storage = sctx->storage;
+ int ret;
+
+ i_assert(!sctx->finished);
+ sctx->finished = TRUE;
+
+ i_assert(storage->v.save_finish != NULL);
+ ret = storage->v.save_finish(sctx);
+ if (ret < 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Failed to upload script: %s",
+ storage->error);
+
+ sctx->failed = TRUE;
+ }
+ return ret;
+}
+
+void sieve_storage_save_set_mtime(struct sieve_storage_save_context *sctx,
+ time_t mtime)
+{
+ sctx->mtime = mtime;
+}
+
+struct sieve_script *
+sieve_storage_save_get_tempscript(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_storage *storage = sctx->storage;
+
+ if (sctx->failed)
+ return NULL;
+
+ if (sctx->scriptobject != NULL)
+ return sctx->scriptobject;
+
+ i_assert(storage->v.save_get_tempscript != NULL);
+ sctx->scriptobject = storage->v.save_get_tempscript(sctx);
+
+ i_assert(sctx->scriptobject != NULL ||
+ storage->error_code != SIEVE_ERROR_NONE);
+ return sctx->scriptobject;
+}
+
+bool sieve_storage_save_will_activate(struct sieve_storage_save_context *sctx)
+{
+ if (sctx->scriptname == NULL)
+ return FALSE;
+
+ if (sctx->active_scriptname == NULL) {
+ const char *scriptname;
+
+ if (sieve_storage_active_script_get_name(sctx->storage,
+ &scriptname) > 0) {
+ sctx->active_scriptname =
+ p_strdup(sctx->pool, scriptname);
+ }
+ }
+
+ /* Is the requested script active? */
+ return (sctx->active_scriptname != NULL &&
+ strcmp(sctx->scriptname, sctx->active_scriptname) == 0);
+}
+
+int sieve_storage_save_commit(struct sieve_storage_save_context **_sctx)
+{
+ struct sieve_storage_save_context *sctx = *_sctx;
+ struct sieve_storage *storage;
+ const char *scriptname;
+ bool default_activate = FALSE;
+ int ret;
+
+ *_sctx = NULL;
+ if (sctx == NULL)
+ return 0;
+
+ storage = sctx->storage;
+ scriptname = sctx->scriptname;
+
+ i_assert(!sctx->failed);
+ i_assert(sctx->finished);
+ i_assert(sctx->scriptname != NULL);
+
+ /* Check whether we're replacing the default active script */
+ if (storage->default_name != NULL &&
+ storage->default_location != NULL &&
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 &&
+ strcmp(sctx->scriptname, storage->default_name) == 0 &&
+ sieve_storage_save_will_activate(sctx) &&
+ sieve_storage_check_script_direct(storage, storage->default_name,
+ NULL) <= 0)
+ default_activate = TRUE;
+
+ sieve_storage_save_cleanup(sctx);
+
+ i_assert(storage->v.save_commit != NULL);
+ ret = storage->v.save_commit(sctx);
+
+ /* Implicitly activate it when we're replacing the default
+ active script */
+ if (ret >= 0 && default_activate) {
+ struct sieve_script *script;
+ enum sieve_error error;
+
+ script = sieve_storage_open_script(storage, scriptname, &error);
+ if (script == NULL) {
+ /* Somehow not actually saved */
+ ret = (error == SIEVE_ERROR_NOT_FOUND ? 0 : -1);
+ } else if (sieve_script_activate(script, (time_t)-1) < 0) {
+ /* Failed to activate; roll back */
+ ret = -1;
+ (void)sieve_script_delete(script, TRUE);
+ }
+ if (script != NULL)
+ sieve_script_unref(&script);
+
+ if (ret < 0) {
+ e_error(sctx->event,
+ "Failed to implicitly activate script `%s' "
+ "while replacing the default active script",
+ scriptname);
+ }
+ }
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Finished saving script");
+
+ /* set INBOX mailbox attribute */
+ (void)sieve_storage_sync_script_save(storage, scriptname);
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_save_finished");
+
+ e_debug(e->event(), "Failed to save script: %s",
+ storage->error);
+ }
+
+ sieve_storage_save_deinit(&sctx);
+ return ret;
+}
+
+void sieve_storage_save_cancel(struct sieve_storage_save_context **_sctx)
+{
+ struct sieve_storage_save_context *sctx = *_sctx;
+ struct sieve_storage *storage;
+
+ *_sctx = NULL;
+ if (sctx == NULL)
+ return;
+
+ storage = sctx->storage;
+
+ sctx->failed = TRUE;
+
+ sieve_storage_save_cleanup(sctx);
+
+ if (!sctx->finished)
+ (void)sieve_storage_save_finish(sctx);
+
+ struct event_passthrough *e =
+ event_create_passthrough(sctx->event)->
+ add_str("error", "Canceled")->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Canceled saving script");
+
+ i_assert(storage->v.save_cancel != NULL);
+ storage->v.save_cancel(sctx);
+
+ sieve_storage_save_deinit(&sctx);
+}
+
+int sieve_storage_save_as_active(struct sieve_storage *storage,
+ struct istream *input, time_t mtime)
+{
+ struct event *event;
+ int ret;
+
+ event = event_create(storage->event);
+ event_set_append_log_prefix(event, "active script: save: ");
+
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("sieve_storage_save_started");
+ e_debug(e->event(), "Started saving active script");
+
+ i_assert(storage->v.save_as_active != NULL);
+ ret = storage->v.save_as_active(storage, input, mtime);
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Finished saving active script");
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Failed to save active script: %s",
+ storage->error);
+ }
+
+ event_unref(&event);
+ return ret;
+}
+
+int sieve_storage_save_as(struct sieve_storage *storage, struct istream *input,
+ const char *name)
+{
+ struct event *event;
+ int ret;
+
+ event = sieve_storage_save_create_event(storage, name);
+
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("sieve_storage_save_started");
+ e_debug(e->event(), "Started saving script");
+
+ i_assert(storage->v.save_as != NULL);
+ ret = storage->v.save_as(storage, input, name);
+
+ if (ret >= 0) {
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Finished saving sieve script");
+ } else {
+ struct event_passthrough *e =
+ event_create_passthrough(event)->
+ add_str("error", storage->error)->
+ set_name("sieve_storage_save_finished");
+ e_debug(e->event(), "Failed to save script: %s",
+ storage->error);
+ }
+
+ event_unref(&event);
+ return ret;
+}
+
+/*
+ * Checking quota
+ */
+
+bool sieve_storage_quota_validsize(struct sieve_storage *storage, size_t size,
+ uint64_t *limit_r)
+{
+ uint64_t max_size;
+
+ max_size = sieve_max_script_size(storage->svinst);
+ if (max_size > 0 && size > max_size) {
+ *limit_r = max_size;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+uint64_t sieve_storage_quota_max_script_size(struct sieve_storage *storage)
+{
+ return sieve_max_script_size(storage->svinst);
+}
+
+int sieve_storage_quota_havespace(struct sieve_storage *storage,
+ const char *scriptname, size_t size,
+ enum sieve_storage_quota *quota_r,
+ uint64_t *limit_r)
+{
+ *quota_r = SIEVE_STORAGE_QUOTA_NONE;
+ *limit_r = 0;
+
+ /* Check the script size */
+ if (!sieve_storage_quota_validsize(storage, size, limit_r)) {
+ *quota_r = SIEVE_STORAGE_QUOTA_MAXSIZE;
+ return 0;
+ }
+
+ /* Do we need to scan the storage (quota enabled) ? */
+ if (storage->max_scripts == 0 && storage->max_storage == 0)
+ return 1;
+
+ if (storage->v.quota_havespace == NULL)
+ return 1;
+
+ return storage->v.quota_havespace(storage, scriptname, size,
+ quota_r, limit_r);
+}
+
+/*
+ * Properties
+ */
+
+const char *sieve_storage_location(const struct sieve_storage *storage)
+{
+ return storage->location;
+}
+
+bool sieve_storage_is_default(const struct sieve_storage *storage)
+{
+ return storage->is_default;
+}
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_clear_error(struct sieve_storage *storage)
+{
+ i_free(storage->error);
+ storage->error_code = SIEVE_ERROR_NONE;
+ storage->error = NULL;
+}
+
+void sieve_storage_set_error(struct sieve_storage *storage,
+ enum sieve_error error, const char *fmt, ...)
+{
+ va_list va;
+
+ sieve_storage_clear_error(storage);
+
+ if (fmt != NULL) {
+ va_start(va, fmt);
+ storage->error = i_strdup_vprintf(fmt, va);
+ va_end(va);
+ }
+
+ storage->error_code = error;
+}
+
+void sieve_storage_copy_error(struct sieve_storage *storage,
+ const struct sieve_storage *source)
+{
+ sieve_storage_clear_error(storage);
+ storage->error = i_strdup(source->error);
+ storage->error_code = source->error_code;
+}
+
+void sieve_storage_set_internal_error(struct sieve_storage *storage)
+{
+ struct tm *tm;
+ char str[256];
+
+ sieve_storage_clear_error(storage);
+
+ /* critical errors may contain sensitive data, so let user
+ see only "Internal error" with a timestamp to make it
+ easier to look from log files the actual error message. */
+ tm = localtime(&ioloop_time);
+
+ storage->error =
+ (strftime(str, sizeof(str), CRITICAL_MSG_STAMP, tm) > 0 ?
+ i_strdup(str) : i_strdup(CRITICAL_MSG));
+
+ storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+}
+
+void sieve_storage_set_critical(struct sieve_storage *storage,
+ const char *fmt, ...)
+{
+ va_list va;
+
+ if (fmt != NULL) {
+ if ((storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0) {
+ va_start(va, fmt);
+ e_error(storage->svinst->event, "%s storage: %s",
+ storage->driver_name,
+ t_strdup_vprintf(fmt, va));
+ va_end(va);
+
+ sieve_storage_set_internal_error(storage);
+
+ } else {
+ sieve_storage_clear_error(storage);
+
+ /* no user is involved while synchronizing, so do it the
+ normal way */
+ va_start(va, fmt);
+ storage->error = i_strdup_vprintf(fmt, va);
+ va_end(va);
+
+ storage->error_code = SIEVE_ERROR_TEMP_FAILURE;
+ }
+ }
+}
+
+const char *
+sieve_storage_get_last_error(struct sieve_storage *storage,
+ enum sieve_error *error_r)
+{
+ /* We get here only in error situations, so we have to return some
+ error. If storage->error is NULL, it means we forgot to set it at
+ some point..
+ */
+
+ if (error_r != NULL)
+ *error_r = storage->error_code;
+
+ return storage->error != NULL ? storage->error : "Unknown error";
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-storage.h b/pigeonhole/src/lib-sieve/sieve-storage.h
new file mode 100644
index 0000000..125a86e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-storage.h
@@ -0,0 +1,187 @@
+#ifndef SIEVE_STORAGE_H
+#define SIEVE_STORAGE_H
+
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE \
+ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER"sieve/"
+#define MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES \
+ MAILBOX_ATTRIBUTE_PREFIX_SIEVE"files/"
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT \
+ MAILBOX_ATTRIBUTE_PREFIX_SIEVE"default"
+
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK 'L'
+#define MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT 'S'
+
+/*
+ * Storage object
+ */
+
+enum sieve_storage_flags {
+ /* Storage is opened for read/write access (e.g. ManageSieve) */
+ SIEVE_STORAGE_FLAG_READWRITE = 0x01,
+ /* This storage is used for synchronization (and not normal ManageSieve)
+ */
+ SIEVE_STORAGE_FLAG_SYNCHRONIZING = 0x02
+};
+
+struct sieve_storage;
+
+struct sieve_storage *
+sieve_storage_create(struct sieve_instance *svinst, const char *location,
+ enum sieve_storage_flags flags, enum sieve_error *error_r)
+ ATTR_NULL(4);
+struct sieve_storage *
+sieve_storage_create_main(struct sieve_instance *svinst, struct mail_user *user,
+ enum sieve_storage_flags flags,
+ enum sieve_error *error_r) ATTR_NULL(4);
+
+void sieve_storage_ref(struct sieve_storage *storage);
+void sieve_storage_unref(struct sieve_storage **_storage);
+
+/*
+ * Script access
+ */
+
+struct sieve_script *
+sieve_storage_get_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r) ATTR_NULL(3);
+struct sieve_script *
+sieve_storage_open_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r) ATTR_NULL(3);
+int sieve_storage_check_script(struct sieve_storage *storage, const char *name,
+ enum sieve_error *error_r) ATTR_NULL(3);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *
+sieve_storage_get_script_sequence(struct sieve_storage *storage,
+ enum sieve_error *error_r);
+
+/*
+ * Active script
+ */
+
+int sieve_storage_active_script_get_name(struct sieve_storage *storage,
+ const char **name_r);
+
+struct sieve_script *
+sieve_storage_active_script_open(struct sieve_storage *storage,
+ enum sieve_error *error_r) ATTR_NULL(2);
+
+int sieve_storage_active_script_get_last_change(struct sieve_storage *storage,
+ time_t *last_change_r);
+
+/*
+ * Listing scripts in storage
+ */
+
+struct sieve_storage_list_context;
+
+/* Create a context for listing the scripts in the storage */
+struct sieve_storage_list_context *
+sieve_storage_list_init(struct sieve_storage *storage);
+/* Get the next script in the storage. */
+const char *sieve_storage_list_next(struct sieve_storage_list_context *lctx,
+ bool *active_r) ATTR_NULL(2);
+/* Destroy the listing context */
+int sieve_storage_list_deinit(struct sieve_storage_list_context **lctx);
+
+/*
+ * Saving scripts in storage
+ */
+
+struct sieve_storage_save_context;
+
+struct sieve_storage_save_context *
+sieve_storage_save_init(struct sieve_storage *storage, const char *scriptname,
+ struct istream *input);
+
+int sieve_storage_save_continue(struct sieve_storage_save_context *sctx);
+
+int sieve_storage_save_finish(struct sieve_storage_save_context *sctx);
+
+struct sieve_script *
+sieve_storage_save_get_tempscript(struct sieve_storage_save_context *sctx);
+
+bool sieve_storage_save_will_activate(struct sieve_storage_save_context *sctx);
+
+void sieve_storage_save_set_mtime(struct sieve_storage_save_context *sctx,
+ time_t mtime);
+
+void sieve_storage_save_cancel(struct sieve_storage_save_context **sctx);
+
+int sieve_storage_save_commit(struct sieve_storage_save_context **sctx);
+
+int sieve_storage_save_as(struct sieve_storage *storage, struct istream *input,
+ const char *name);
+
+/* Saves input directly as a regular file at the active script path.
+ * This is needed for the doveadm-sieve plugin.
+ */
+int sieve_storage_save_as_active(struct sieve_storage *storage,
+ struct istream *input, time_t mtime);
+
+/*
+ * Management
+ */
+
+int sieve_storage_deactivate(struct sieve_storage *storage, time_t mtime);
+
+/*
+ * Storage quota
+ */
+
+enum sieve_storage_quota {
+ SIEVE_STORAGE_QUOTA_NONE,
+ SIEVE_STORAGE_QUOTA_MAXSIZE,
+ SIEVE_STORAGE_QUOTA_MAXSCRIPTS,
+ SIEVE_STORAGE_QUOTA_MAXSTORAGE
+};
+
+bool sieve_storage_quota_validsize(struct sieve_storage *storage, size_t size,
+ uint64_t *limit_r);
+
+uint64_t sieve_storage_quota_max_script_size(struct sieve_storage *storage);
+
+int sieve_storage_quota_havespace(struct sieve_storage *storage,
+ const char *scriptname, size_t size,
+ enum sieve_storage_quota *quota_r,
+ uint64_t *limit_r);
+
+/*
+ * Properties
+ */
+
+const char *sieve_storage_location(const struct sieve_storage *storage)
+ ATTR_PURE;
+bool sieve_storage_is_default(const struct sieve_storage *storage) ATTR_PURE;
+
+int sieve_storage_is_singular(struct sieve_storage *storage);
+
+/*
+ * Error handling
+ */
+
+void sieve_storage_clear_error(struct sieve_storage *storage);
+
+void sieve_storage_set_error(struct sieve_storage *storage,
+ enum sieve_error error, const char *fmt, ...)
+ ATTR_FORMAT(3, 4);
+void sieve_storage_set_critical(struct sieve_storage *storage,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+const char *sieve_storage_get_last_error(struct sieve_storage *storage,
+ enum sieve_error *error_r)
+ ATTR_NULL(2);
+
+/*
+ *
+ */
+
+int sieve_storage_get_last_change(struct sieve_storage *storage,
+ time_t *last_change_r);
+void sieve_storage_set_modified(struct sieve_storage *storage,
+ time_t mtime);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-stringlist.c b/pigeonhole/src/lib-sieve/sieve-stringlist.c
new file mode 100644
index 0000000..17b7d5c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-stringlist.c
@@ -0,0 +1,276 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+
+/*
+ * Default implementation
+ */
+
+int sieve_stringlist_read_all
+(struct sieve_stringlist *strlist, pool_t pool,
+ const char * const **list_r)
+{
+ if ( strlist->read_all == NULL ) {
+ ARRAY(const char *) items;
+ string_t *item;
+ int ret;
+
+ sieve_stringlist_reset(strlist);
+
+ p_array_init(&items, pool, 4);
+
+ item = NULL;
+ while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+ const char *stritem = p_strdup(pool, str_c(item));
+
+ array_append(&items, &stritem, 1);
+ }
+
+ (void)array_append_space(&items);
+ *list_r = array_idx(&items, 0);
+
+ return ( ret < 0 ? -1 : 1 );
+ }
+
+ return strlist->read_all(strlist, pool, list_r);
+}
+
+int sieve_stringlist_get_length
+(struct sieve_stringlist *strlist)
+{
+ if ( strlist->get_length == NULL ) {
+ string_t *item;
+ int count = 0;
+ int ret;
+
+ sieve_stringlist_reset(strlist);
+ while ( (ret=sieve_stringlist_next_item(strlist, &item)) > 0 ) {
+ count++;
+ }
+ sieve_stringlist_reset(strlist);
+
+ return ( ret < 0 ? -1 : count );
+ }
+
+ return strlist->get_length(strlist);
+}
+
+/*
+ * Single Stringlist
+ */
+
+/* Object */
+
+static int sieve_single_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_single_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int sieve_single_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+
+struct sieve_single_stringlist {
+ struct sieve_stringlist strlist;
+
+ string_t *value;
+
+ bool end:1;
+ bool count_empty:1;
+};
+
+struct sieve_stringlist *sieve_single_stringlist_create
+(const struct sieve_runtime_env *renv, string_t *str, bool count_empty)
+{
+ struct sieve_single_stringlist *strlist;
+
+ strlist = t_new(struct sieve_single_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = sieve_single_stringlist_next_item;
+ strlist->strlist.reset = sieve_single_stringlist_reset;
+ strlist->strlist.get_length = sieve_single_stringlist_get_length;
+ strlist->count_empty = count_empty;
+ strlist->value = str;
+
+ return &strlist->strlist;
+}
+
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty)
+{
+ string_t *str = t_str_new_const(cstr, strlen(cstr));
+
+ return sieve_single_stringlist_create(renv, str, count_empty);
+}
+
+/* Implementation */
+
+static int sieve_single_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct sieve_single_stringlist *strlist =
+ (struct sieve_single_stringlist *)_strlist;
+
+ if ( strlist->end ) {
+ *str_r = NULL;
+ return 0;
+ }
+
+ *str_r = strlist->value;
+ strlist->end = TRUE;
+ return 1;
+}
+
+static void sieve_single_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_single_stringlist *strlist =
+ (struct sieve_single_stringlist *)_strlist;
+
+ strlist->end = FALSE;
+}
+
+static int sieve_single_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_single_stringlist *strlist =
+ (struct sieve_single_stringlist *)_strlist;
+
+ return ( strlist->count_empty || str_len(strlist->value) > 0 ? 1 : 0 );
+}
+
+/*
+ * Index Stringlist
+ */
+
+/* Object */
+
+static int sieve_index_stringlist_next_item
+ (struct sieve_stringlist *_strlist, string_t **str_r);
+static void sieve_index_stringlist_reset
+ (struct sieve_stringlist *_strlist);
+static int sieve_index_stringlist_get_length
+ (struct sieve_stringlist *_strlist);
+static void sieve_index_stringlist_set_trace
+ (struct sieve_stringlist *strlist, bool trace);
+
+struct sieve_index_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_stringlist *source;
+
+ int index;
+ bool end:1;
+};
+
+struct sieve_stringlist *sieve_index_stringlist_create
+(const struct sieve_runtime_env *renv, struct sieve_stringlist *source,
+ int index)
+{
+ struct sieve_index_stringlist *strlist;
+
+ strlist = t_new(struct sieve_index_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = sieve_index_stringlist_next_item;
+ strlist->strlist.reset = sieve_index_stringlist_reset;
+ strlist->strlist.get_length = sieve_index_stringlist_get_length;
+ strlist->strlist.set_trace = sieve_index_stringlist_set_trace;
+ strlist->source = source;
+ strlist->index = index;
+
+ return &strlist->strlist;
+}
+
+/* Implementation */
+
+static int sieve_index_stringlist_next_item
+(struct sieve_stringlist *_strlist, string_t **str_r)
+{
+ struct sieve_index_stringlist *strlist =
+ (struct sieve_index_stringlist *)_strlist;
+ int index, ret;
+
+ if ( strlist->end ) {
+ *str_r = NULL;
+ return 0;
+ }
+
+ if ( strlist->index < 0 ) {
+ int len = sieve_stringlist_get_length(strlist->source);
+ if (len < 0) {
+ _strlist->exec_status = strlist->source->exec_status;
+ return -1;
+ }
+
+ if (len < -strlist->index) {
+ *str_r = NULL;
+ strlist->end = TRUE;
+ return 0;
+ }
+ index = len + 1 + strlist->index;
+ } else {
+ index = strlist->index;
+ }
+
+ i_assert(index > 0);
+ while ( index > 0 ) {
+ if ( (ret=sieve_stringlist_next_item(strlist->source, str_r)) <= 0 ) {
+ if (ret < 0)
+ _strlist->exec_status = strlist->source->exec_status;
+ return ret;
+ }
+
+ index--;
+ }
+
+ strlist->end = TRUE;
+ return 1;
+}
+
+static void sieve_index_stringlist_reset
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_index_stringlist *strlist =
+ (struct sieve_index_stringlist *)_strlist;
+
+ sieve_stringlist_reset(strlist->source);
+ strlist->end = FALSE;
+}
+
+static int sieve_index_stringlist_get_length
+(struct sieve_stringlist *_strlist)
+{
+ struct sieve_index_stringlist *strlist =
+ (struct sieve_index_stringlist *)_strlist;
+ int len;
+
+ len = sieve_stringlist_get_length(strlist->source);
+ if (len < 0) {
+ _strlist->exec_status = strlist->source->exec_status;
+ return -1;
+ }
+
+ if ( strlist->index < 0 ) {
+ if ( -strlist->index >= len )
+ return 0;
+ } else if ( strlist->index >= len ) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void sieve_index_stringlist_set_trace
+(struct sieve_stringlist *_strlist, bool trace)
+{
+ struct sieve_index_stringlist *strlist =
+ (struct sieve_index_stringlist *)_strlist;
+
+ sieve_stringlist_set_trace(strlist->source, trace);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-stringlist.h b/pigeonhole/src/lib-sieve/sieve-stringlist.h
new file mode 100644
index 0000000..1909b7a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-stringlist.h
@@ -0,0 +1,74 @@
+#ifndef SIEVE_STRINGLIST_H
+#define SIEVE_STRINGLIST_H
+
+/*
+ * Stringlist API
+ */
+
+struct sieve_stringlist {
+ int (*next_item)
+ (struct sieve_stringlist *strlist, string_t **str_r);
+ void (*reset)
+ (struct sieve_stringlist *strlist);
+ int (*get_length)
+ (struct sieve_stringlist *strlist);
+
+ int (*read_all)
+ (struct sieve_stringlist *strlist, pool_t pool,
+ const char * const **list_r);
+
+ void (*set_trace)
+ (struct sieve_stringlist *strlist, bool trace);
+
+ const struct sieve_runtime_env *runenv;
+ int exec_status;
+
+ bool trace:1;
+};
+
+static inline void sieve_stringlist_set_trace
+(struct sieve_stringlist *strlist, bool trace)
+{
+ strlist->trace = trace;
+
+ if ( strlist->set_trace != NULL )
+ strlist->set_trace(strlist, trace);
+}
+
+static inline int sieve_stringlist_next_item
+(struct sieve_stringlist *strlist, string_t **str_r)
+{
+ return strlist->next_item(strlist, str_r);
+}
+
+static inline void sieve_stringlist_reset
+(struct sieve_stringlist *strlist)
+{
+ strlist->reset(strlist);
+}
+
+int sieve_stringlist_get_length
+ (struct sieve_stringlist *strlist);
+
+int sieve_stringlist_read_all
+ (struct sieve_stringlist *strlist, pool_t pool,
+ const char * const **list_r);
+
+/*
+ * Single Stringlist
+ */
+
+struct sieve_stringlist *sieve_single_stringlist_create
+ (const struct sieve_runtime_env *renv, string_t *str, bool count_empty);
+struct sieve_stringlist *sieve_single_stringlist_create_cstr
+(const struct sieve_runtime_env *renv, const char *cstr, bool count_empty);
+
+/*
+ * Index Stringlist
+ */
+
+struct sieve_stringlist *sieve_index_stringlist_create
+ (const struct sieve_runtime_env *renv, struct sieve_stringlist *source,
+ int index);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-types.h b/pigeonhole/src/lib-sieve/sieve-types.h
new file mode 100644
index 0000000..439b26e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-types.h
@@ -0,0 +1,316 @@
+#ifndef SIEVE_TYPES_H
+#define SIEVE_TYPES_H
+
+#include "lib.h"
+#include "smtp-address.h"
+
+#include <stdio.h>
+
+/*
+ * Forward declarations
+ */
+
+struct smtp_params_mail;
+struct smtp_params_rcpt;
+
+struct sieve_instance;
+struct sieve_callbacks;
+
+struct sieve_script;
+struct sieve_binary;
+
+struct sieve_message_data;
+struct sieve_script_env;
+struct sieve_exec_status;
+struct sieve_trace_log;
+
+/*
+ * System environment
+ */
+
+enum sieve_flag {
+ /* Relative paths are resolved to HOME */
+ SIEVE_FLAG_HOME_RELATIVE = (1 << 0)
+};
+
+/* Sieve evaluation can be performed at various different points as messages
+ are processed. */
+enum sieve_env_location {
+ /* Unknown */
+ SIEVE_ENV_LOCATION_UNKNOWN = 0,
+ /* "MDA" - evaluation is being performed by a Mail Delivery Agent */
+ SIEVE_ENV_LOCATION_MDA,
+ /* "MTA" - the Sieve script is being evaluated by a Message Transfer Agent */
+ SIEVE_ENV_LOCATION_MTA,
+ /* "MS" - evaluation is being performed by a Message Store */
+ SIEVE_ENV_LOCATION_MS
+};
+
+/* The point relative to final delivery where the Sieve script is being
+ evaluated. */
+enum sieve_delivery_phase {
+ SIEVE_DELIVERY_PHASE_UNKNOWN = 0,
+ SIEVE_DELIVERY_PHASE_PRE,
+ SIEVE_DELIVERY_PHASE_DURING,
+ SIEVE_DELIVERY_PHASE_POST,
+};
+
+struct sieve_environment {
+ const char *hostname;
+ const char *domainname;
+
+ const char *base_dir;
+ const char *username;
+ const char *home_dir;
+ const char *temp_dir;
+
+ struct event *event_parent;
+
+ enum sieve_flag flags;
+ enum sieve_env_location location;
+ enum sieve_delivery_phase delivery_phase;
+};
+
+/*
+ * Callbacks
+ */
+
+struct sieve_callbacks {
+ const char *(*get_homedir)(void *context);
+ const char *(*get_setting)(void *context, const char *identifier);
+};
+
+/*
+ * Errors
+ */
+
+enum sieve_error {
+ SIEVE_ERROR_NONE = 0,
+
+ /* Temporary internal error */
+ SIEVE_ERROR_TEMP_FAILURE,
+ /* It's not possible to do the wanted operation */
+ SIEVE_ERROR_NOT_POSSIBLE,
+ /* Invalid parameters (eg. script name not valid) */
+ SIEVE_ERROR_BAD_PARAMS,
+ /* No permission to do the request */
+ SIEVE_ERROR_NO_PERMISSION,
+ /* Out of disk space */
+ SIEVE_ERROR_NO_QUOTA,
+ /* Item (e.g. script or binary) cannot be found */
+ SIEVE_ERROR_NOT_FOUND,
+ /* Item (e.g. script or binary) already exists */
+ SIEVE_ERROR_EXISTS,
+ /* Referenced item (e.g. script or binary) is not valid or currupt */
+ SIEVE_ERROR_NOT_VALID,
+ /* Not allowed to perform the operation because the item is in active use */
+ SIEVE_ERROR_ACTIVE,
+ /* Operation exceeds resource limit */
+ SIEVE_ERROR_RESOURCE_LIMIT,
+};
+
+/*
+ * Compile flags
+ */
+
+enum sieve_compile_flags {
+ /* No global extensions are allowed
+ * (as marked by sieve_global_extensions setting)
+ */
+ SIEVE_COMPILE_FLAG_NOGLOBAL = (1<<0),
+ /* Script is being uploaded (usually through ManageSieve) */
+ SIEVE_COMPILE_FLAG_UPLOADED = (1<<1),
+ /* Script is being activated (usually through ManageSieve) */
+ SIEVE_COMPILE_FLAG_ACTIVATED = (1<<2),
+ /* Compiled for environment with no access to envelope */
+ SIEVE_COMPILE_FLAG_NO_ENVELOPE = (1<<3)
+};
+
+/*
+ * Message data
+ *
+ * - The mail message + envelope data
+ */
+
+struct sieve_message_data {
+ struct mail *mail;
+
+ const char *auth_user;
+ const char *id;
+
+ struct {
+ const struct smtp_address *mail_from;
+ const struct smtp_params_mail *mail_params;
+
+ const struct smtp_address *rcpt_to;
+ const struct smtp_params_rcpt *rcpt_params;
+ } envelope;
+};
+
+/*
+ * Runtime flags
+ */
+
+enum sieve_execute_flags {
+ /* No global extensions are allowed
+ * (as marked by sieve_global_extensions setting)
+ */
+ SIEVE_EXECUTE_FLAG_NOGLOBAL = (1<<0),
+ /* Do not execute (implicit keep) at the end */
+ SIEVE_EXECUTE_FLAG_DEFER_KEEP = (1<<1),
+ /* There is no envelope */
+ SIEVE_EXECUTE_FLAG_NO_ENVELOPE = (1<<2),
+ /* Skip sending responses */
+ SIEVE_EXECUTE_FLAG_SKIP_RESPONSES = (1<<3),
+ /* Log result as info (when absent, only debug logging is performed) */
+ SIEVE_EXECUTE_FLAG_LOG_RESULT = (1<<4),
+};
+
+/*
+ * Runtime trace settings
+ */
+
+typedef enum {
+ SIEVE_TRLVL_NONE = 0,
+ SIEVE_TRLVL_ACTIONS,
+ SIEVE_TRLVL_COMMANDS,
+ SIEVE_TRLVL_TESTS,
+ SIEVE_TRLVL_MATCHING
+} sieve_trace_level_t;
+
+enum {
+ SIEVE_TRFLG_DEBUG = (1 << 0),
+ SIEVE_TRFLG_ADDRESSES = (1 << 1)
+};
+
+struct sieve_trace_config {
+ sieve_trace_level_t level;
+ unsigned int flags;
+};
+
+/*
+ * Duplicate checking
+ */
+
+enum sieve_duplicate_check_result {
+ SIEVE_DUPLICATE_CHECK_RESULT_EXISTS = 1,
+ SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND = 0,
+ SIEVE_DUPLICATE_CHECK_RESULT_FAILURE = -1,
+ SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE = -2,
+};
+
+/*
+ * Script environment
+ *
+ * - Environment for currently executing script
+ */
+
+struct sieve_script_env {
+ /* Mail-related */
+ struct mail_user *user;
+ const struct message_address *postmaster_address;
+ const char *default_mailbox;
+ bool mailbox_autocreate;
+ bool mailbox_autosubscribe;
+
+ /* External context data */
+
+ void *script_context;
+
+ /* Callbacks */
+
+ /* Interface for sending mail */
+ void *(*smtp_start)
+ (const struct sieve_script_env *senv,
+ const struct smtp_address *mail_from);
+ /* Add a new recipient */
+ void (*smtp_add_rcpt)
+ (const struct sieve_script_env *senv, void *handle,
+ const struct smtp_address *rcpt_to);
+ /* Get an output stream where the message can be written to. The recipients
+ must already be added before calling this. */
+ struct ostream *(*smtp_send)
+ (const struct sieve_script_env *senv, void *handle);
+ /* Abort the SMTP transaction after smtp_send() is already issued */
+ void (*smtp_abort)
+ (const struct sieve_script_env *senv, void *handle);
+ /* Returns 1 on success, 0 on permanent failure, -1 on temporary failure. */
+ int (*smtp_finish)
+ (const struct sieve_script_env *senv, void *handle,
+ const char **error_r);
+
+ /* Interface for marking and checking duplicates */
+ void *(*duplicate_transaction_begin)(
+ const struct sieve_script_env *senv);
+ void (*duplicate_transaction_commit)(void **_dup_trans);
+ void (*duplicate_transaction_rollback)(void **_dup_trans);
+
+ enum sieve_duplicate_check_result
+ (*duplicate_check)(void *dup_trans, const struct sieve_script_env *senv,
+ const void *id, size_t id_size);
+ void (*duplicate_mark)(void *dup_trans,
+ const struct sieve_script_env *senv,
+ const void *id, size_t id_size, time_t time);
+
+ /* Interface for rejecting mail */
+ int (*reject_mail)(const struct sieve_script_env *senv,
+ const struct smtp_address *recipient, const char *reason);
+
+ /* Interface for amending result messages */
+ const char *
+ (*result_amend_log_message)(const struct sieve_script_env *senv,
+ enum log_type log_type,
+ const char *message);
+
+ /* Execution status record */
+ struct sieve_exec_status *exec_status;
+
+ /* Runtime trace*/
+ struct sieve_trace_log *trace_log;
+ struct sieve_trace_config trace_config;
+};
+
+#define SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) \
+ (senv->default_mailbox == NULL ? "INBOX" : senv->default_mailbox )
+
+/*
+ * Resource usage
+ */
+
+struct sieve_resource_usage {
+ /* The total amount of system + user CPU time consumed while executing
+ the Sieve script. */
+ unsigned int cpu_time_msecs;
+};
+
+/*
+ * Script execution status
+ */
+
+struct sieve_exec_status {
+ struct mail_storage *last_storage;
+
+ struct sieve_resource_usage resource_usage;
+
+ bool message_saved:1;
+ bool message_forwarded:1;
+ bool tried_default_save:1;
+ bool keep_original:1;
+ bool store_failed:1;
+ bool significant_action_executed:1;
+};
+
+/*
+ * Execution exit codes
+ */
+
+enum sieve_execution_exitcode {
+ SIEVE_EXEC_OK = 1,
+ SIEVE_EXEC_FAILURE = 0,
+ SIEVE_EXEC_TEMP_FAILURE = -1,
+ SIEVE_EXEC_BIN_CORRUPT = -2,
+ SIEVE_EXEC_KEEP_FAILED = -3,
+ SIEVE_EXEC_RESOURCE_LIMIT = -4,
+};
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve-validator.c b/pigeonhole/src/lib-sieve/sieve-validator.c
new file mode 100644
index 0000000..bef4a7d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-validator.c
@@ -0,0 +1,1706 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+#include "buffer.h"
+#include "mempool.h"
+#include "hash.h"
+
+#include "sieve-common.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+
+#include "sieve-comparators.h"
+#include "sieve-address-parts.h"
+
+/*
+ * Forward declarations
+ */
+
+static void
+sieve_validator_register_core_commands(struct sieve_validator *valdtr);
+static void
+sieve_validator_register_core_tests(struct sieve_validator *valdtr);
+
+/*
+ * Types
+ */
+
+/* Tag registration */
+
+struct sieve_tag_registration {
+ const struct sieve_argument_def *tag_def;
+ const struct sieve_extension *ext;
+
+ const char *identifier;
+ int id_code;
+};
+
+/* Command registration */
+
+struct sieve_command_registration {
+ const struct sieve_command_def *cmd_def;
+ const struct sieve_extension *ext;
+
+ ARRAY(struct sieve_tag_registration *) normal_tags;
+ ARRAY(struct sieve_tag_registration *) instanced_tags;
+ ARRAY(struct sieve_tag_registration *) persistent_tags;
+};
+
+/* Default (literal) arguments */
+
+struct sieve_default_argument {
+ const struct sieve_argument_def *arg_def;
+ const struct sieve_extension *ext;
+
+ struct sieve_default_argument *overrides;
+};
+
+/*
+ * Validator extension
+ */
+
+struct sieve_validator_extension_reg {
+ const struct sieve_validator_extension *valext;
+ const struct sieve_extension *ext;
+ struct sieve_ast_argument *arg;
+ void *context;
+
+ bool loaded:1;
+ bool required:1;
+};
+
+/*
+ * Validator
+ */
+
+struct sieve_validator {
+ pool_t pool;
+
+ struct sieve_instance *svinst;
+ struct sieve_ast *ast;
+ struct sieve_script *script;
+ enum sieve_compile_flags flags;
+
+ struct sieve_error_handler *ehandler;
+
+ bool finished_require;
+
+ /* Registries */
+
+ HASH_TABLE(const char *, struct sieve_command_registration *) commands;
+
+ ARRAY(struct sieve_validator_extension_reg) extensions;
+
+ /* This is currently a wee bit ugly and needs more thought */
+ struct sieve_default_argument default_arguments[SAT_COUNT];
+
+ /* Default argument processing state (FIXME: ugly) */
+ struct sieve_default_argument *current_defarg;
+ enum sieve_argument_type current_defarg_type;
+ bool current_defarg_constant;
+};
+
+/*
+ * Validator object
+ */
+
+struct sieve_validator *
+sieve_validator_create(struct sieve_ast *ast,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags)
+{
+ pool_t pool;
+ struct sieve_validator *valdtr;
+ const struct sieve_extension *const *ext_preloaded;
+ unsigned int i, ext_count;
+
+ pool = pool_alloconly_create("sieve_validator", 16384);
+ valdtr = p_new(pool, struct sieve_validator, 1);
+ valdtr->pool = pool;
+
+ valdtr->ehandler = ehandler;
+ sieve_error_handler_ref(ehandler);
+
+ valdtr->ast = ast;
+ sieve_ast_ref(ast);
+
+ valdtr->script = sieve_ast_script(ast);
+ valdtr->svinst = sieve_script_svinst(valdtr->script);
+ valdtr->flags = flags;
+
+ /* Setup default arguments */
+ valdtr->default_arguments[SAT_NUMBER].arg_def = &number_argument;
+ valdtr->default_arguments[SAT_NUMBER].ext = NULL;
+ valdtr->default_arguments[SAT_VAR_STRING].arg_def = &string_argument;
+ valdtr->default_arguments[SAT_VAR_STRING].ext = NULL;
+ valdtr->default_arguments[SAT_CONST_STRING].arg_def = &string_argument;
+ valdtr->default_arguments[SAT_CONST_STRING].ext = NULL;
+ valdtr->default_arguments[SAT_STRING_LIST].arg_def = &string_list_argument;
+ valdtr->default_arguments[SAT_STRING_LIST].ext = NULL;
+
+ /* Setup storage for extension contexts */
+ p_array_init(&valdtr->extensions, pool,
+ sieve_extensions_get_count(valdtr->svinst));
+
+ /* Setup command registry */
+ hash_table_create(&valdtr->commands, pool, 0, strcase_hash, strcasecmp);
+ sieve_validator_register_core_commands(valdtr);
+ sieve_validator_register_core_tests(valdtr);
+
+ /* Pre-load core language features implemented as 'extensions' */
+ ext_preloaded =
+ sieve_extensions_get_preloaded(valdtr->svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ const struct sieve_extension_def *ext_def =
+ ext_preloaded[i]->def;
+
+ if (ext_def != NULL && ext_def->validator_load != NULL)
+ (void)ext_def->validator_load(ext_preloaded[i], valdtr);
+ }
+
+ return valdtr;
+}
+
+void sieve_validator_free(struct sieve_validator **valdtr)
+{
+ const struct sieve_validator_extension_reg *extrs;
+ unsigned int ext_count, i;
+
+ hash_table_destroy(&(*valdtr)->commands);
+ sieve_ast_unref(&(*valdtr)->ast);
+
+ sieve_error_handler_unref(&(*valdtr)->ehandler);
+
+ /* Signal registered extensions that the validator is being destroyed */
+ extrs = array_get(&(*valdtr)->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extrs[i].valext != NULL && extrs[i].valext->free != NULL)
+ extrs[i].valext->free(extrs[i].ext, *valdtr,
+ extrs[i].context);
+ }
+
+ pool_unref(&(*valdtr)->pool);
+
+ *valdtr = NULL;
+}
+
+/*
+ * Accessors
+ */
+
+// FIXME: build validate environment
+
+pool_t sieve_validator_pool(struct sieve_validator *valdtr)
+{
+ return valdtr->pool;
+}
+
+struct sieve_error_handler *
+sieve_validator_error_handler(struct sieve_validator *valdtr)
+{
+ return valdtr->ehandler;
+}
+
+struct sieve_ast *sieve_validator_ast(struct sieve_validator *valdtr)
+{
+ return valdtr->ast;
+}
+
+struct sieve_script *sieve_validator_script(struct sieve_validator *valdtr)
+{
+ return valdtr->script;
+}
+
+struct sieve_instance *sieve_validator_svinst(struct sieve_validator *valdtr)
+{
+ return valdtr->svinst;
+}
+
+enum sieve_compile_flags
+sieve_validator_compile_flags(struct sieve_validator *valdtr)
+{
+ return valdtr->flags;
+}
+
+/*
+ * Command registry
+ */
+
+/* Dummy command object to mark unknown commands in the registry */
+
+static bool _cmd_unknown_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ i_unreached();
+ return FALSE;
+}
+
+static const struct sieve_command_def unknown_command = {
+ .identifier = "",
+ .type = SCT_NONE,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = _cmd_unknown_validate
+};
+
+/* Registration of the core commands of the language */
+
+static void
+sieve_validator_register_core_tests(struct sieve_validator *valdtr)
+{
+ unsigned int i;
+
+ for (i = 0; i < sieve_core_tests_count; i++) {
+ sieve_validator_register_command(valdtr, NULL,
+ sieve_core_tests[i]);
+ }
+}
+
+static void
+sieve_validator_register_core_commands(struct sieve_validator *valdtr)
+{
+ unsigned int i;
+
+ for (i = 0; i < sieve_core_commands_count; i++) {
+ sieve_validator_register_command(valdtr, NULL,
+ sieve_core_commands[i]);
+ }
+}
+
+/* Registry functions */
+
+static struct sieve_command_registration *
+sieve_validator_find_command_registration(struct sieve_validator *valdtr,
+ const char *command)
+{
+ return hash_table_lookup(valdtr->commands, command);
+}
+
+static struct sieve_command_registration *
+_sieve_validator_register_command(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def,
+ const char *identifier)
+{
+ struct sieve_command_registration *cmd_reg =
+ p_new(valdtr->pool, struct sieve_command_registration, 1);
+
+ cmd_reg->cmd_def = cmd_def;
+ cmd_reg->ext = ext;
+
+ hash_table_insert(valdtr->commands, identifier, cmd_reg);
+
+ return cmd_reg;
+}
+
+void sieve_validator_register_command(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_command_def *cmd_def)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(
+ valdtr, cmd_def->identifier);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, ext, cmd_def, cmd_def->identifier);
+ } else {
+ cmd_reg->cmd_def = cmd_def;
+ cmd_reg->ext = ext;
+ }
+
+ if (cmd_def->registered != NULL)
+ cmd_def->registered(valdtr, ext, cmd_reg);
+}
+
+static void
+sieve_validator_register_unknown_command(struct sieve_validator *valdtr,
+ const char *command)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ if (cmd_reg == NULL) {
+ (void)_sieve_validator_register_command(
+ valdtr, NULL, &unknown_command, command);
+ } else {
+ i_assert(cmd_reg->cmd_def == NULL);
+ cmd_reg->cmd_def = &unknown_command;
+ }
+}
+
+/*const struct sieve_command *sieve_validator_find_command
+(struct sieve_validator *valdtr, const char *command)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ return ( record == NULL ? NULL : record->command );
+}*/
+
+/*
+ * Per-command tagged argument registry
+ */
+
+/* Dummy argument object to mark unknown arguments in the registry */
+
+static bool
+_unknown_tag_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_ast_argument **arg ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED)
+{
+ i_unreached();
+ return FALSE;
+}
+
+static const struct sieve_argument_def _unknown_tag = {
+ .identifier = "",
+ .validate = _unknown_tag_validate,
+};
+
+static inline bool
+_tag_registration_is_unknown(struct sieve_tag_registration *tag_reg)
+{
+ return (tag_reg != NULL && tag_reg->tag_def == &_unknown_tag);
+}
+
+/* Registry functions */
+
+static void
+_sieve_validator_register_tag(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def,
+ const char *identifier, int id_code)
+{
+ struct sieve_tag_registration *reg;
+
+ reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = id_code;
+ if (identifier == NULL)
+ reg->identifier = tag_def->identifier;
+ else
+ reg->identifier = p_strdup(valdtr->pool, identifier);
+
+ if (!array_is_created(&cmd_reg->normal_tags))
+ p_array_init(&cmd_reg->normal_tags, valdtr->pool, 4);
+
+ array_append(&cmd_reg->normal_tags, &reg, 1);
+}
+
+void sieve_validator_register_persistent_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def)
+{
+ /* Add the tag to the persistent tags list if necessary */
+ if (tag_def->validate_persistent != NULL) {
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(
+ valdtr, command);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, NULL, NULL, command);
+ }
+
+ struct sieve_tag_registration *reg;
+
+ if (!array_is_created(&cmd_reg->persistent_tags)) {
+ p_array_init(&cmd_reg->persistent_tags,
+ valdtr->pool, 4);
+ } else {
+ struct sieve_tag_registration *reg_idx;
+
+ /* Avoid dupplicate registration */
+ array_foreach_elem(&cmd_reg->persistent_tags, reg_idx) {
+ if (reg_idx->tag_def == tag_def)
+ return;
+ }
+ }
+
+ reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = -1;
+
+ array_append(&cmd_reg->persistent_tags, &reg, 1);
+ }
+}
+
+void sieve_validator_register_external_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def, int id_code)
+{
+ struct sieve_command_registration *cmd_reg =
+ sieve_validator_find_command_registration(valdtr, command);
+
+ if (cmd_reg == NULL) {
+ cmd_reg = _sieve_validator_register_command(
+ valdtr, NULL, NULL, command);
+ }
+
+ _sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
+ NULL, id_code);
+}
+
+void sieve_validator_register_tag(
+ struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def, int id_code)
+{
+ if (tag_def->is_instance_of == NULL) {
+ _sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
+ NULL, id_code);
+ } else {
+ struct sieve_tag_registration *reg =
+ p_new(valdtr->pool, struct sieve_tag_registration, 1);
+ reg->ext = ext;
+ reg->tag_def = tag_def;
+ reg->id_code = id_code;
+
+ if (!array_is_created(&cmd_reg->instanced_tags))
+ p_array_init(&cmd_reg->instanced_tags, valdtr->pool, 4);
+
+ array_append(&cmd_reg->instanced_tags, &reg, 1);
+ }
+}
+
+static void
+sieve_validator_register_unknown_tag(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const char *tag)
+{
+ _sieve_validator_register_tag(valdtr, cmd_reg, NULL,
+ &_unknown_tag, tag, 0);
+}
+
+static struct sieve_tag_registration *
+_sieve_validator_command_tag_get(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ const char *tag, void **data)
+{
+ struct sieve_command_registration *cmd_reg = cmd->reg;
+ struct sieve_tag_registration * const *regs;
+ unsigned int i, reg_count;
+
+ /* First check normal tags */
+ if (array_is_created(&cmd_reg->normal_tags)) {
+ regs = array_get(&cmd_reg->normal_tags, &reg_count);
+
+ for (i = 0; i < reg_count; i++) {
+ if (regs[i]->tag_def != NULL &&
+ strcasecmp(regs[i]->identifier, tag) == 0) {
+
+ return regs[i];
+ }
+ }
+ }
+
+ /* Not found so far, try the instanced tags */
+ if (array_is_created(&cmd_reg->instanced_tags)) {
+ regs = array_get(&cmd_reg->instanced_tags, &reg_count);
+
+ for (i = 0; i < reg_count; i++) {
+ if (regs[i]->tag_def != NULL) {
+ if (regs[i]->tag_def->is_instance_of(
+ valdtr, cmd, regs[i]->ext, tag, data))
+ return regs[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static bool
+sieve_validator_command_tag_exists(struct sieve_validator *valdtr,
+ struct sieve_command *cmd, const char *tag)
+{
+ return (_sieve_validator_command_tag_get(valdtr, cmd,
+ tag, NULL) != NULL);
+}
+
+static struct sieve_tag_registration *
+sieve_validator_command_tag_get(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, void **data)
+{
+ const char *tag = sieve_ast_argument_tag(arg);
+
+ return _sieve_validator_command_tag_get(valdtr, cmd, tag, data);
+}
+
+/*
+ * Extension support
+ */
+
+static bool
+sieve_validator_extensions_check_conficts(struct sieve_validator *valdtr,
+ struct sieve_ast_argument *ext_arg,
+ const struct sieve_extension *ext)
+{
+ struct sieve_validator_extension_reg *ext_reg;
+ struct sieve_validator_extension_reg *regs;
+ unsigned int count, i;
+
+ if (ext->id < 0)
+ return TRUE;
+
+ ext_reg = array_idx_get_space(&valdtr->extensions,
+ (unsigned int) ext->id);
+
+ regs = array_get_modifiable(&valdtr->extensions, &count);
+ for (i = 0; i < count; i++) {
+ bool required = ext_reg->required && regs[i].required;
+
+ if (regs[i].ext == NULL)
+ continue;
+ if (regs[i].ext == ext)
+ continue;
+ if (!regs[i].loaded)
+ continue;
+
+ /* Check this extension vs other extension */
+ if (ext_reg->valext != NULL &&
+ ext_reg->valext->check_conflict != NULL) {
+ struct sieve_ast_argument *this_ext_arg =
+ (ext_arg == NULL ? regs[i].arg : ext_arg);
+
+ if (!ext_reg->valext->check_conflict(
+ ext, valdtr, ext_reg->context, this_ext_arg,
+ regs[i].ext, required))
+ return FALSE;
+ }
+
+ /* Check other extension vs this extension */
+ if (regs[i].valext != NULL &&
+ regs[i].valext->check_conflict != NULL) {
+ if (!regs[i].valext->check_conflict(
+ regs[i].ext, valdtr, regs[i].context,
+ regs[i].arg, ext, required))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+bool sieve_validator_extension_load(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const struct sieve_extension *ext,
+ bool required)
+{
+ const struct sieve_extension_def *extdef = ext->def;
+ struct sieve_validator_extension_reg *reg = NULL;
+
+ if (ext->global &&
+ (valdtr->flags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0) {
+ const char *cmd_prefix = (cmd == NULL ? "" :
+ t_strdup_printf("%s %s: ",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd)));
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%sfailed to load Sieve capability `%s': "
+ "its use is restricted to global scripts",
+ cmd_prefix, sieve_extension_name(ext));
+ return FALSE;
+ }
+
+ /* Register extension no matter what and store the
+ * AST argument registering it */
+ if (ext->id >= 0) {
+ reg = array_idx_get_space(&valdtr->extensions,
+ (unsigned int)ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->required = reg->required || required;
+ if (reg->arg == NULL)
+ reg->arg = ext_arg;
+ }
+
+ if (extdef->validator_load != NULL &&
+ !extdef->validator_load(ext, valdtr)) {
+ const char *cmd_prefix = (cmd == NULL ? "" :
+ t_strdup_printf("%s %s: ",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd)));
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%sfailed to load Sieve capability `%s'",
+ cmd_prefix, sieve_extension_name(ext));
+ return FALSE;
+ }
+
+ /* Check conflicts with other extensions */
+ if (!sieve_validator_extensions_check_conficts(valdtr, ext_arg, ext))
+ return FALSE;
+
+ /* Link extension to AST for use at code generation */
+ if (reg != NULL) {
+ sieve_ast_extension_link(valdtr->ast, ext, reg->required);
+ reg->loaded = TRUE;
+ }
+
+ return TRUE;
+}
+
+const struct sieve_extension *
+sieve_validator_extension_load_by_name(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const char *ext_name)
+{
+ const struct sieve_extension *ext;
+
+ ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+ if (ext == NULL || ext->def == NULL || !ext->enabled) {
+ unsigned int i;
+ bool core_test = FALSE;
+ bool core_command = FALSE;
+
+ for (i = 0; !core_command && i < sieve_core_commands_count;
+ i++) {
+ if (strcasecmp(sieve_core_commands[i]->identifier,
+ ext_name) == 0)
+ core_command = TRUE;
+ }
+
+ for (i = 0; !core_test && i < sieve_core_tests_count; i++) {
+ if (strcasecmp(sieve_core_tests[i]->identifier,
+ ext_name) == 0)
+ core_test = TRUE;
+ }
+
+ if (core_test || core_command) {
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%s %s: `%s' is not known as a Sieve capability, "
+ "but it is known as a Sieve %s that is always available",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ str_sanitize(ext_name, 128),
+ (core_test ? "test" : "command"));
+ } else {
+ sieve_argument_validate_error(
+ valdtr, ext_arg,
+ "%s %s: unknown Sieve capability `%s'",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ str_sanitize(ext_name, 128));
+ }
+ return NULL;
+ }
+
+ if (!sieve_validator_extension_load(valdtr, cmd, ext_arg, ext, TRUE))
+ return NULL;
+
+ return ext;
+}
+
+const struct sieve_extension *
+sieve_validator_extension_load_implicit(struct sieve_validator *valdtr,
+ const char *ext_name)
+{
+ const struct sieve_extension *ext;
+
+ ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);
+
+ if (ext == NULL || ext->def == NULL)
+ return NULL;
+
+ if (!sieve_validator_extension_load(valdtr, NULL, NULL, ext, TRUE))
+ return NULL;
+
+ return ext;
+}
+
+void sieve_validator_extension_register(
+ struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_validator_extension *valext, void *context)
+{
+ struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+ i_assert(reg->ext == NULL || reg->ext == ext);
+ reg->ext = ext;
+ reg->valext = valext;
+ reg->context = context;
+}
+
+bool sieve_validator_extension_loaded(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
+ return FALSE;
+
+ reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+ return (reg->loaded);
+}
+
+void sieve_validator_extension_set_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ void *context)
+{
+ struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0)
+ return;
+
+ reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
+ reg->context = context;
+}
+
+void *sieve_validator_extension_get_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ const struct sieve_validator_extension_reg *reg;
+
+ if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
+ return NULL;
+
+ reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);
+
+ return reg->context;
+}
+
+/*
+ * Overriding the default literal arguments
+ */
+
+void sieve_validator_argument_override(struct sieve_validator *valdtr,
+ enum sieve_argument_type type,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *arg_def)
+{
+ struct sieve_default_argument *arg;
+
+ if (valdtr->default_arguments[type].arg_def != NULL) {
+ arg = p_new(valdtr->pool, struct sieve_default_argument, 1);
+ *arg = valdtr->default_arguments[type];
+
+ valdtr->default_arguments[type].overrides = arg;
+ }
+
+ valdtr->default_arguments[type].arg_def = arg_def;
+ valdtr->default_arguments[type].ext = ext;
+}
+
+static bool
+sieve_validator_argument_default_activate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_default_argument *defarg,
+ struct sieve_ast_argument *arg)
+{
+ bool result = TRUE;
+ struct sieve_default_argument *prev_defarg;
+
+ prev_defarg = valdtr->current_defarg;
+ valdtr->current_defarg = defarg;
+
+ if (arg->argument == NULL) {
+ arg->argument = sieve_argument_create(arg->ast, defarg->arg_def,
+ defarg->ext, 0);
+ } else {
+ arg->argument->def = defarg->arg_def;
+ arg->argument->ext = defarg->ext;
+ }
+
+ if (defarg->arg_def != NULL && defarg->arg_def->validate != NULL)
+ result = defarg->arg_def->validate(valdtr, &arg, cmd);
+
+ valdtr->current_defarg = prev_defarg;
+
+ return result;
+}
+
+bool sieve_validator_argument_activate_super(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ bool constant ATTR_UNUSED)
+{
+ struct sieve_default_argument *defarg;
+
+ if (valdtr->current_defarg == NULL ||
+ valdtr->current_defarg->overrides == NULL)
+ return FALSE;
+
+ if (valdtr->current_defarg->overrides->arg_def == &string_argument) {
+ switch (valdtr->current_defarg_type) {
+ case SAT_CONST_STRING:
+ if (!valdtr->current_defarg_constant) {
+ valdtr->current_defarg_type = SAT_VAR_STRING;
+ defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+ } else {
+ defarg = valdtr->current_defarg->overrides;
+ }
+ break;
+ case SAT_VAR_STRING:
+ defarg = valdtr->current_defarg->overrides;
+ break;
+ default:
+ return FALSE;
+ }
+ } else {
+ defarg = valdtr->current_defarg->overrides;
+ }
+
+ return sieve_validator_argument_default_activate(valdtr, cmd,
+ defarg, arg);
+}
+
+/*
+ * Argument Validation API
+ */
+
+bool sieve_validator_argument_activate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ bool constant)
+{
+ struct sieve_default_argument *defarg;
+
+ switch (sieve_ast_argument_type(arg)) {
+ case SAAT_NUMBER:
+ valdtr->current_defarg_type = SAT_NUMBER;
+ break;
+ case SAAT_STRING:
+ valdtr->current_defarg_type = SAT_CONST_STRING;
+ break;
+ case SAAT_STRING_LIST:
+ valdtr->current_defarg_type = SAT_STRING_LIST;
+ break;
+ default:
+ return FALSE;
+ }
+
+ valdtr->current_defarg_constant = constant;
+ defarg = &valdtr->default_arguments[valdtr->current_defarg_type];
+
+ if (!constant && defarg->arg_def == &string_argument) {
+ valdtr->current_defarg_type = SAT_VAR_STRING;
+ defarg = &valdtr->default_arguments[SAT_VAR_STRING];
+ }
+
+ return sieve_validator_argument_default_activate(valdtr, cmd,
+ defarg, arg);
+}
+
+bool sieve_validate_positional_argument(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ const char *arg_name,
+ unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type)
+{
+ i_assert(arg != NULL);
+
+ if (sieve_ast_argument_type(arg) != req_type &&
+ (sieve_ast_argument_type(arg) != SAAT_STRING ||
+ req_type != SAAT_STRING_LIST))
+ {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "the %s %s expects %s as argument %d (%s), "
+ "but %s was found",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type),
+ arg_pos, arg_name, sieve_ast_argument_name(arg));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool sieve_validate_tag_parameter(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *tag,
+ struct sieve_ast_argument *param,
+ const char *arg_name, unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type,
+ bool constant)
+{
+ i_assert(tag != NULL);
+
+ if (param == NULL) {
+ const char *position = (arg_pos == 0 ? "" :
+ t_strdup_printf(" %d (%s)", arg_pos, arg_name));
+
+ sieve_argument_validate_error(
+ valdtr, tag,
+ "the :%s tag for the %s %s requires %s as parameter%s, "
+ "but no parameters were found",
+ sieve_ast_argument_tag(tag),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type), position);
+ return FALSE;
+ }
+
+ if (sieve_ast_argument_type(param) != req_type &&
+ (sieve_ast_argument_type(param) != SAAT_STRING ||
+ req_type != SAAT_STRING_LIST))
+ {
+ const char *position = (arg_pos == 0 ? "" :
+ t_strdup_printf(" %d (%s)", arg_pos, arg_name));
+
+ sieve_argument_validate_error(
+ valdtr, param,
+ "the :%s tag for the %s %s requires %s as parameter%s, "
+ "but %s was found",
+ sieve_ast_argument_tag(tag),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ sieve_ast_argument_type_name(req_type), position,
+ sieve_ast_argument_name(param));
+ return FALSE;
+ }
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, param, constant))
+ return FALSE;
+
+ param->argument->id_code = tag->argument->id_code;
+
+ return TRUE;
+}
+
+/*
+ * Command argument validation
+ */
+
+static bool
+sieve_validate_command_arguments(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ int arg_count = cmd->def->positional_args;
+ int real_count = 0;
+ struct sieve_ast_argument *arg;
+ struct sieve_command_registration *cmd_reg = cmd->reg;
+
+ /* Resolve tagged arguments */
+ arg = sieve_ast_argument_first(cmd->ast_node);
+ while (arg != NULL) {
+ void *arg_data = NULL;
+ struct sieve_tag_registration *tag_reg;
+ const struct sieve_argument_def *tag_def;
+
+ if (sieve_ast_argument_type(arg) != SAAT_TAG) {
+ arg = sieve_ast_argument_next(arg);
+ continue;
+ }
+
+ tag_reg = sieve_validator_command_tag_get(valdtr, cmd,
+ arg, &arg_data);
+
+ if (tag_reg == NULL) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "unknown tagged argument ':%s' for the %s %s "
+ "(reported only once at first occurrence)",
+ sieve_ast_argument_tag(arg),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ sieve_validator_register_unknown_tag(
+ valdtr, cmd_reg, sieve_ast_argument_tag(arg));
+ return FALSE;
+ }
+
+ /* Check whether previously tagged as unknown */
+ if (_tag_registration_is_unknown(tag_reg))
+ return FALSE;
+
+ tag_def = tag_reg->tag_def;
+
+ /* Assign the tagged argument type to the ast for later
+ reference */
+ arg->argument = sieve_argument_create(
+ arg->ast, tag_def, tag_reg->ext, tag_reg->id_code);
+ arg->argument->data = arg_data;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Validate tagged arguments */
+ arg = sieve_ast_argument_first(cmd->ast_node);
+ while (arg != NULL && sieve_ast_argument_type(arg) == SAAT_TAG) {
+ const struct sieve_argument_def *tag_def = arg->argument->def;
+ struct sieve_ast_argument *parg;
+
+ /* Scan backwards for any duplicates */
+ if ((tag_def->flags & SIEVE_ARGUMENT_FLAG_MULTIPLE) == 0) {
+ parg = sieve_ast_argument_prev(arg);
+ while (parg != NULL) {
+ if ((sieve_ast_argument_type(parg) == SAAT_TAG &&
+ parg->argument->def == tag_def) ||
+ (arg->argument->id_code > 0 &&
+ parg->argument != NULL &&
+ parg->argument->id_code == arg->argument->id_code))
+ {
+ const char *tag_id = sieve_ast_argument_tag(arg);
+ const char *tag_desc =
+ strcmp(tag_def->identifier, tag_id) != 0 ?
+ t_strdup_printf("%s argument (:%s)",
+ tag_def->identifier, tag_id) :
+ t_strdup_printf(":%s argument",
+ tag_def->identifier);
+
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "encountered duplicate %s for the %s %s",
+ tag_desc, sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+
+ return FALSE;
+ }
+
+ parg = sieve_ast_argument_prev(parg);
+ }
+ }
+
+ /* Call the validation function for the tag (if present)
+ Fail if the validation fails:
+ Let's not whine multiple times about a single command
+ having multiple bad arguments...
+ */
+ if (tag_def->validate != NULL) {
+ if (!tag_def->validate(valdtr, &arg, cmd))
+ return FALSE;
+ } else {
+ arg = sieve_ast_argument_next(arg);
+ }
+ }
+
+ /* Remaining arguments should be positional (tags are not allowed
+ here) */
+ cmd->first_positional = arg;
+
+ while (arg != NULL) {
+ if (sieve_ast_argument_type(arg) == SAAT_TAG) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "encountered an unexpected tagged argument ':%s' "
+ "while validating positional arguments for the %s %s",
+ sieve_ast_argument_tag(arg),
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+
+ real_count++;
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ /* Check the required count versus the real number of arguments */
+ if (arg_count >= 0 && real_count != arg_count) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires %d positional argument(s), "
+ "but %d is/are specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd),
+ arg_count, real_count);
+ return FALSE;
+ }
+
+ /* Call initial validation for persistent arguments */
+ if (array_is_created(&cmd_reg->persistent_tags)) {
+ struct sieve_tag_registration * const *regs;
+ unsigned int i, reg_count;
+
+ regs = array_get(&cmd_reg->persistent_tags, &reg_count);
+ for (i = 0; i < reg_count; i++) {
+ const struct sieve_argument_def *tag_def =
+ regs[i]->tag_def;
+
+ if (tag_def != NULL &&
+ tag_def->validate_persistent != NULL) {
+ /* To be sure */
+ if (!tag_def->validate_persistent(
+ valdtr, cmd, regs[i]->ext))
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_arguments_context(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg =
+ sieve_command_first_argument(cmd);
+
+ while (arg != NULL) {
+ const struct sieve_argument *argument = arg->argument;
+
+ if (argument != NULL && argument->def != NULL &&
+ argument->def->validate_context != NULL) {
+
+ if (!argument->def->validate_context(valdtr, arg, cmd))
+ return FALSE;
+ }
+
+ arg = sieve_ast_argument_next(arg);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Command Validation API
+ */
+
+static bool
+sieve_validate_command_subtests(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ const unsigned int count)
+{
+ switch (count) {
+ case 0:
+ if (sieve_ast_test_count(cmd->ast_node) > 0) {
+ /* Unexpected command specified */
+ enum sieve_command_type ctype = SCT_NONE;
+ struct sieve_command_registration *cmd_reg;
+ struct sieve_ast_node *test =
+ sieve_ast_test_first(cmd->ast_node);
+
+ cmd_reg = sieve_validator_find_command_registration(
+ valdtr, test->identifier);
+
+ /* First check what we are dealing with */
+ if (cmd_reg != NULL && cmd_reg->cmd_def != NULL)
+ ctype = cmd_reg->cmd_def->type;
+
+ switch (ctype) {
+ case SCT_TEST: /* Spurious test */
+ case SCT_HYBRID:
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s accepts no sub-tests, "
+ "but tests are specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ case SCT_NONE: /* Unknown command */
+ /* Is it perhaps a tag for which the ':' was
+ omitted ? */
+ if (sieve_validator_command_tag_exists(
+ valdtr, cmd, test->identifier)) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "missing colon ':' before ':%s' tag in %s %s",
+ test->identifier,
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ }
+ /* Fall through */
+ case SCT_COMMAND:
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "missing semicolon ';' after %s %s",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ break;
+ }
+ return FALSE;
+ }
+ break;
+ case 1:
+ if (sieve_ast_test_count(cmd->ast_node) == 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires one sub-test, "
+ "but none is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+
+ } else if (sieve_ast_test_count(cmd->ast_node) > 1 ||
+ cmd->ast_node->test_list) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires one sub-test, "
+ "but a list of tests is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+ break;
+ default:
+ if (sieve_ast_test_count(cmd->ast_node) == 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires a list of sub-tests, "
+ "but none is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ } else if (sieve_ast_test_count(cmd->ast_node) == 1 &&
+ !cmd->ast_node->test_list) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s %s requires a list of sub-tests, "
+ "but a single test is specified",
+ sieve_command_identifier(cmd),
+ sieve_command_type_name(cmd));
+ return FALSE;
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_command_block(struct sieve_validator *valdtr,
+ struct sieve_command *cmd, bool block_allowed,
+ bool block_required)
+{
+ i_assert(cmd->ast_node->type == SAT_COMMAND);
+
+ if (block_required) {
+ if (!cmd->ast_node->block) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s command requires a command block, "
+ "but it is missing",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+ } else if (!block_allowed && cmd->ast_node->block) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the %s command does not accept a command block, "
+ "but one is specified anyway",
+ sieve_command_identifier(cmd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * AST Validation
+ */
+
+static bool
+sieve_validate_test_list(struct sieve_validator *valdtr,
+ struct sieve_ast_node *test_list, int *const_r);
+static bool
+sieve_validate_block(struct sieve_validator *valdtr,
+ struct sieve_ast_node *block);
+static bool
+sieve_validate_command(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node, int *const_r);
+
+static bool
+sieve_validate_command_context(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node)
+{
+ enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+ struct sieve_command_registration *cmd_reg;
+
+ i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);
+
+ /* Verify the command specified by this node */
+ cmd_reg = sieve_validator_find_command_registration(
+ valdtr, cmd_node->identifier);
+
+ if (cmd_reg != NULL && cmd_reg->cmd_def != NULL) {
+ const struct sieve_command_def *cmd_def = cmd_reg->cmd_def;
+
+ /* Identifier = "" when the command was previously marked as
+ unknown */
+ if (*(cmd_def->identifier) != '\0') {
+ if ((cmd_def->type == SCT_COMMAND && ast_type == SAT_TEST) ||
+ (cmd_def->type == SCT_TEST && ast_type == SAT_COMMAND)) {
+ sieve_validator_error(
+ valdtr, cmd_node->source_line,
+ "attempted to use %s '%s' as %s",
+ sieve_command_def_type_name(cmd_def),
+ cmd_node->identifier,
+ sieve_ast_type_name(ast_type));
+ return FALSE;
+ }
+
+ cmd_node->command = sieve_command_create(
+ cmd_node, cmd_reg->ext, cmd_def, cmd_reg);
+ } else {
+ return FALSE;
+ }
+ } else {
+ sieve_validator_error(
+ valdtr, cmd_node->source_line,
+ "unknown %s '%s' (only reported once at first occurrence)",
+ sieve_ast_type_name(ast_type), cmd_node->identifier);
+
+ sieve_validator_register_unknown_command(
+ valdtr, cmd_node->identifier);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static bool
+sieve_validate_command(struct sieve_validator *valdtr,
+ struct sieve_ast_node *cmd_node, int *const_r)
+{
+ enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
+ struct sieve_command *cmd =
+ (cmd_node == NULL ? NULL : cmd_node->command);
+ const struct sieve_command_def *cmd_def =
+ (cmd != NULL ? cmd->def : NULL);
+ bool result = TRUE;
+
+ i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);
+
+ if (cmd_def != NULL && *(cmd_def->identifier) != '\0') {
+ if (cmd_def->pre_validate == NULL ||
+ cmd_def->pre_validate(valdtr, cmd)) {
+ /* Check argument syntax */
+ if (!sieve_validate_command_arguments(valdtr, cmd)) {
+ result = FALSE;
+
+ /* A missing ':' causes a tag to become a test.
+ This can be the cause of the arguments
+ validation failing. Therefore we must produce
+ an error for the sub-tests as well if
+ appropriate. */
+ (void)sieve_validate_command_subtests(
+ valdtr, cmd, cmd_def->subtests);
+ } else if (!sieve_validate_command_subtests(
+ valdtr, cmd, cmd_def->subtests) ||
+ (ast_type == SAT_COMMAND &&
+ !sieve_validate_command_block(
+ valdtr, cmd, cmd_def->block_allowed,
+ cmd_def->block_required))) {
+ result = FALSE;
+ } else {
+ /* Call command validation function if specified
+ */
+ if (cmd_def->validate != NULL) {
+ result = cmd_def->validate(valdtr, cmd) &&
+ result;
+ }
+ }
+ } else {
+ /* If pre-validation fails, don't bother to validate
+ further as context might be missing and doing so is
+ not very useful for further error reporting anyway */
+ return FALSE;
+ }
+
+ result = result && sieve_validate_arguments_context(valdtr, cmd);
+ }
+
+ /*
+ * Descend further into the AST
+ */
+
+ if (cmd_def != NULL) {
+ /* Tests */
+ if (cmd_def->subtests > 0) {
+ if (result ||
+ sieve_errors_more_allowed(valdtr->ehandler)) {
+ result = sieve_validate_test_list(
+ valdtr, cmd_node, const_r) && result;
+ }
+ } else if (result) {
+ if (cmd_def->validate_const != NULL) {
+ (void)cmd_def->validate_const(
+ valdtr, cmd, const_r, -1);
+ } else {
+ *const_r = -1;
+ }
+ }
+
+ /* Skip block if result of test is const FALSE */
+ if (result && *const_r == 0)
+ return TRUE;
+
+ /* Command block */
+ if (cmd_def->block_allowed && ast_type == SAT_COMMAND &&
+ (result || sieve_errors_more_allowed(valdtr->ehandler))) {
+ result = sieve_validate_block(valdtr, cmd_node) &&
+ result;
+ }
+ }
+
+ return result;
+}
+
+static bool
+sieve_validate_test_list(struct sieve_validator *valdtr,
+ struct sieve_ast_node *test_node, int *const_r)
+{
+ struct sieve_command *tst = test_node->command;
+ const struct sieve_command_def *tst_def =
+ (tst != NULL ? tst->def : NULL);
+ struct sieve_ast_node *test;
+ bool result = TRUE;
+
+ if (tst_def != NULL && tst_def->validate_const != NULL) {
+ if (!tst_def->validate_const(valdtr, tst, const_r, -2))
+ return TRUE;
+ }
+
+ test = sieve_ast_test_first(test_node);
+ while (test != NULL &&
+ (result || sieve_errors_more_allowed(valdtr->ehandler))) {
+ int const_value = -2;
+
+ result = sieve_validate_command_context(valdtr, test) &&
+ sieve_validate_command(valdtr, test, &const_value) &&
+ result;
+
+ if (result) {
+ if (tst_def != NULL &&
+ tst_def->validate_const != NULL) {
+ if (!tst_def->validate_const(
+ valdtr, tst, const_r, const_value))
+ return TRUE;
+ } else {
+ *const_r = -1;
+ }
+ }
+
+ if (result && const_value >= 0)
+ test = sieve_ast_node_detach(test);
+ else
+ test = sieve_ast_test_next(test);
+ }
+
+ return result;
+}
+
+static bool
+sieve_validate_block(struct sieve_validator *valdtr,
+ struct sieve_ast_node *block)
+{
+ bool result = TRUE, fatal = FALSE;
+ struct sieve_ast_node *cmd_node, *next;
+
+ T_BEGIN {
+ cmd_node = sieve_ast_command_first(block);
+ while (!fatal && cmd_node != NULL &&
+ (result ||
+ sieve_errors_more_allowed(valdtr->ehandler))) {
+ bool command_success;
+ int const_value = -2;
+
+ next = sieve_ast_command_next(cmd_node);
+
+ /* Check if this is the first non-require command */
+ if (sieve_ast_node_type(block) == SAT_ROOT &&
+ !valdtr->finished_require &&
+ strcasecmp(cmd_node->identifier,
+ cmd_require.identifier) != 0) {
+ const struct sieve_validator_extension_reg *extrs;
+ const struct sieve_extension *const *exts;
+ unsigned int ext_count, i;
+
+ valdtr->finished_require = TRUE;
+
+ /* Load implicit extensions */
+ exts = sieve_extensions_get_all(valdtr->svinst, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (exts[i]->implicit) {
+ (void)sieve_validator_extension_load(
+ valdtr, NULL, NULL, exts[i], TRUE);
+ }
+ }
+
+ /* Validate all 'require'd extensions */
+ extrs = array_get(&valdtr->extensions, &ext_count);
+ for (i = 0; i < ext_count; i++) {
+ if (extrs[i].loaded && extrs[i].valext != NULL &&
+ extrs[i].valext->validate != NULL) {
+ if (!extrs[i].valext->validate(
+ extrs[i].ext, valdtr,
+ extrs[i].context, extrs[i].arg,
+ extrs[i].required)) {
+ fatal = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ command_success =
+ sieve_validate_command_context(valdtr, cmd_node);
+ result = command_success && result;
+
+ result = !fatal &&
+ sieve_validate_command(valdtr, cmd_node,
+ &const_value) && result;
+
+ cmd_node = next;
+ }
+ } T_END;
+
+ return result && !fatal;
+}
+
+bool sieve_validator_run(struct sieve_validator *valdtr)
+{
+ return sieve_validate_block(valdtr, sieve_ast_root(valdtr->ast));
+}
+
+/*
+ * Validator object registry
+ */
+
+struct sieve_validator_object_reg {
+ const struct sieve_object_def *obj_def;
+ const struct sieve_extension *ext;
+};
+
+struct sieve_validator_object_registry {
+ struct sieve_validator *valdtr;
+ ARRAY(struct sieve_validator_object_reg) registrations;
+};
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_get(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ return (struct sieve_validator_object_registry *)
+ sieve_validator_extension_get_context(valdtr, ext);
+}
+
+void sieve_validator_object_registry_add(
+ struct sieve_validator_object_registry *regs,
+ const struct sieve_extension *ext,
+ const struct sieve_object_def *obj_def)
+{
+ struct sieve_validator_object_reg *reg;
+
+ reg = array_append_space(&regs->registrations);
+ reg->ext = ext;
+ reg->obj_def = obj_def;
+}
+
+bool sieve_validator_object_registry_find(
+ struct sieve_validator_object_registry *regs, const char *identifier,
+ struct sieve_object *obj)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_count(&regs->registrations); i++) {
+ const struct sieve_validator_object_reg *reg =
+ array_idx(&regs->registrations, i);
+
+ if (strcasecmp(reg->obj_def->identifier, identifier) == 0) {
+ if (obj != NULL) {
+ obj->def = reg->obj_def;
+ obj->ext = reg->ext;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_create(struct sieve_validator *valdtr)
+{
+ pool_t pool = valdtr->pool;
+ struct sieve_validator_object_registry *regs =
+ p_new(pool, struct sieve_validator_object_registry, 1);
+
+ /* Setup registry */
+ p_array_init(&regs->registrations, valdtr->pool, 4);
+
+ regs->valdtr = valdtr;
+
+ return regs;
+}
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_init(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext)
+{
+ struct sieve_validator_object_registry *regs =
+ sieve_validator_object_registry_create(valdtr);
+
+ sieve_validator_extension_set_context(valdtr, ext, regs);
+ return regs;
+}
+
+/*
+ * Error handling
+ */
+
+#undef sieve_validator_error
+void sieve_validator_error(struct sieve_validator *valdtr,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_ERROR,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(valdtr->script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(valdtr->ehandler, &params, fmt, args);
+ va_end(args);
+}
+
+#undef sieve_validator_warning
+void sieve_validator_warning(struct sieve_validator *valdtr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+{
+ struct sieve_error_params params = {
+ .log_type = LOG_TYPE_WARNING,
+ .csrc = {
+ .filename = csrc_filename,
+ .linenum = csrc_linenum,
+ },
+ };
+ va_list args;
+
+ params.location =
+ sieve_error_script_location(valdtr->script, source_line);
+
+ va_start(args, fmt);
+ sieve_logv(valdtr->ehandler, &params, fmt, args);
+ va_end(args);
+
+}
diff --git a/pigeonhole/src/lib-sieve/sieve-validator.h b/pigeonhole/src/lib-sieve/sieve-validator.h
new file mode 100644
index 0000000..21b41cc
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve-validator.h
@@ -0,0 +1,197 @@
+#ifndef SIEVE_VALIDATOR_H
+#define SIEVE_VALIDATOR_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Types
+ */
+
+enum sieve_argument_type {
+ SAT_NUMBER,
+ SAT_CONST_STRING,
+ SAT_VAR_STRING,
+ SAT_STRING_LIST,
+
+ SAT_COUNT
+};
+
+struct sieve_command_registration;
+
+/*
+ * Validator
+ */
+
+struct sieve_validator;
+
+struct sieve_validator *
+sieve_validator_create(struct sieve_ast *ast,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags);
+void sieve_validator_free(struct sieve_validator **valdtr);
+pool_t sieve_validator_pool(struct sieve_validator *valdtr);
+
+bool sieve_validator_run(struct sieve_validator *valdtr);
+
+/*
+ * Accessors
+ */
+
+struct sieve_error_handler *
+sieve_validator_error_handler(struct sieve_validator *valdtr);
+struct sieve_ast *sieve_validator_ast(struct sieve_validator *valdtr);
+struct sieve_script *sieve_validator_script(struct sieve_validator *valdtr);
+struct sieve_instance *sieve_validator_svinst(struct sieve_validator *valdtr);
+enum sieve_compile_flags
+sieve_validator_compile_flags(struct sieve_validator *valdtr);
+
+/*
+ * Command/Test registry
+ */
+
+void sieve_validator_register_command(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ const struct sieve_command_def *command);
+
+/*
+ * Per-command tagged argument registry
+ */
+
+void sieve_validator_register_tag(struct sieve_validator *valdtr,
+ struct sieve_command_registration *cmd_reg,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def,
+ int id_code);
+void sieve_validator_register_external_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def, int id_code);
+void sieve_validator_register_persistent_tag(
+ struct sieve_validator *valdtr, const char *command,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *tag_def);
+
+/*
+ * Overriding the default literal arguments
+ */
+
+void sieve_validator_argument_override(
+ struct sieve_validator *valdtr, enum sieve_argument_type type,
+ const struct sieve_extension *ext,
+ const struct sieve_argument_def *arg_def);
+bool sieve_validator_argument_activate_super(
+ struct sieve_validator *valdtr, struct sieve_command *cmd,
+ struct sieve_ast_argument *arg, bool constant);
+
+/*
+ * Argument validation API
+ */
+
+bool sieve_validate_positional_argument(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ const char *arg_name,
+ unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type);
+bool sieve_validator_argument_activate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *arg,
+ bool constant);
+
+bool sieve_validate_tag_parameter(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *tag,
+ struct sieve_ast_argument *param,
+ const char *arg_name, unsigned int arg_pos,
+ enum sieve_ast_argument_type req_type,
+ bool constant);
+
+/*
+ * Extension support
+ */
+
+struct sieve_validator_extension {
+ const struct sieve_extension_def *ext;
+
+ bool (*check_conflict)(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg,
+ const struct sieve_extension *ext_other,
+ bool required);
+ bool (*validate)(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context,
+ struct sieve_ast_argument *require_arg, bool required);
+
+ void (*free)(const struct sieve_extension *ext,
+ struct sieve_validator *valdtr, void *context);
+};
+
+bool sieve_validator_extension_load(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const struct sieve_extension *ext,
+ bool required) ATTR_NULL(2, 3);
+const struct sieve_extension *
+sieve_validator_extension_load_by_name(struct sieve_validator *valdtr,
+ struct sieve_command *cmd,
+ struct sieve_ast_argument *ext_arg,
+ const char *ext_name);
+const struct sieve_extension *
+sieve_validator_extension_load_implicit(struct sieve_validator *valdtr,
+ const char *ext_name);
+
+void sieve_validator_extension_register(
+ struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct sieve_validator_extension *valext, void *context);
+bool sieve_validator_extension_loaded(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext);
+
+void sieve_validator_extension_set_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ void *context);
+void *sieve_validator_extension_get_context(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext);
+
+/*
+ * Validator object registry
+ */
+
+struct sieve_validator_object_registry;
+
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_get(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext);
+void sieve_validator_object_registry_add(
+ struct sieve_validator_object_registry *regs,
+ const struct sieve_extension *ext,
+ const struct sieve_object_def *obj_def);
+bool sieve_validator_object_registry_find(
+ struct sieve_validator_object_registry *regs, const char *identifier,
+ struct sieve_object *obj);
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_create(struct sieve_validator *valdtr);
+struct sieve_validator_object_registry *
+sieve_validator_object_registry_init(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext);
+
+/*
+ * Error handling
+ */
+
+void sieve_validator_error(struct sieve_validator *valdtr,
+ const char *csrc_filename, unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_validator_error(valdtr, ...) \
+ sieve_validator_error(valdtr, __FILE__, __LINE__, __VA_ARGS__)
+void sieve_validator_warning(struct sieve_validator *valdtr,
+ const char *csrc_filename,
+ unsigned int csrc_linenum,
+ unsigned int source_line, const char *fmt, ...)
+ ATTR_FORMAT(5, 6);
+#define sieve_validator_warning(valdtr, ...) \
+ sieve_validator_warning(valdtr, __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/sieve.c b/pigeonhole/src/lib-sieve/sieve.c
new file mode 100644
index 0000000..69827a9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve.c
@@ -0,0 +1,1303 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "buffer.h"
+#include "time-util.h"
+#include "eacces-error.h"
+#include "home-expand.h"
+#include "hostpid.h"
+#include "message-address.h"
+#include "mail-user.h"
+
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-plugins.h"
+
+#include "sieve-address.h"
+#include "sieve-script.h"
+#include "sieve-storage-private.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "sieve-parser.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-binary-dumper.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-limits.h"
+#include "sieve-error-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+
+struct event_category event_category_sieve = {
+ .name = "sieve",
+};
+
+/*
+ * Main Sieve library interface
+ */
+
+struct sieve_instance *
+sieve_init(const struct sieve_environment *env,
+ const struct sieve_callbacks *callbacks, void *context, bool debug)
+{
+ struct sieve_instance *svinst;
+ const char *domain;
+ pool_t pool;
+
+ /* Create Sieve engine instance */
+ pool = pool_alloconly_create("sieve", 8192);
+ svinst = p_new(pool, struct sieve_instance, 1);
+ svinst->pool = pool;
+ svinst->callbacks = callbacks;
+ svinst->context = context;
+ svinst->debug = debug;
+ svinst->base_dir = p_strdup_empty(pool, env->base_dir);
+ svinst->username = p_strdup_empty(pool, env->username);
+ svinst->home_dir = p_strdup_empty(pool, env->home_dir);
+ svinst->temp_dir = p_strdup_empty(pool, env->temp_dir);
+ svinst->flags = env->flags;
+ svinst->env_location = env->location;
+ svinst->delivery_phase = env->delivery_phase;
+
+ svinst->event = event_create(env->event_parent);
+ event_add_category(svinst->event, &event_category_sieve);
+ event_set_forced_debug(svinst->event, debug);
+ event_set_append_log_prefix(svinst->event, "sieve: ");
+ event_add_str(svinst->event, "user", env->username);
+
+ /* Determine domain */
+ if (env->domainname != NULL && *(env->domainname) != '\0')
+ domain = env->domainname;
+ else {
+ /* Fall back to parsing username localpart@domain */
+ domain = svinst->username == NULL ? NULL :
+ strchr(svinst->username, '@');
+ if (domain == NULL || *(domain+1) == '\0') {
+ /* Fall back to parsing hostname host.domain */
+ domain = (env->hostname != NULL ?
+ strchr(env->hostname, '.') : NULL);
+ if (domain == NULL || *(domain+1) == '\0' ||
+ strchr(domain+1, '.') == NULL) {
+ /* Fall back to bare hostname */
+ domain = env->hostname;
+ } else {
+ domain++;
+ }
+ } else {
+ domain++;
+ }
+ }
+ svinst->hostname = p_strdup_empty(pool, env->hostname);
+ svinst->domainname = p_strdup(pool, domain);
+
+ sieve_errors_init(svinst);
+
+ e_debug(svinst->event, "%s version %s initializing",
+ PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+
+ /* Read configuration */
+
+ sieve_settings_load(svinst);
+
+ /* Initialize extensions */
+ if (!sieve_extensions_init(svinst)) {
+ sieve_deinit(&svinst);
+ return NULL;
+ }
+
+ /* Initialize storage classes */
+ sieve_storages_init(svinst);
+
+ /* Initialize plugins */
+ sieve_plugins_load(svinst, NULL, NULL);
+
+ /* Configure extensions */
+ sieve_extensions_configure(svinst);
+
+ return svinst;
+}
+
+void sieve_deinit(struct sieve_instance **_svinst)
+{
+ struct sieve_instance *svinst = *_svinst;
+
+ sieve_plugins_unload(svinst);
+ sieve_storages_deinit(svinst);
+ sieve_extensions_deinit(svinst);
+ sieve_errors_deinit(svinst);
+
+ event_unref(&svinst->event);
+
+ pool_unref(&(svinst)->pool);
+ *_svinst = NULL;
+}
+
+void sieve_set_extensions(struct sieve_instance *svinst, const char *extensions)
+{
+ sieve_extensions_set_string(svinst, extensions, FALSE, FALSE);
+}
+
+const char *
+sieve_get_capabilities(struct sieve_instance *svinst, const char *name)
+{
+ if (name == NULL || *name == '\0')
+ return sieve_extensions_get_string(svinst);
+
+ return sieve_extension_capabilities_get_string(svinst, name);
+}
+
+struct event *sieve_get_event(struct sieve_instance *svinst)
+{
+ return svinst->event;
+}
+
+/*
+ * Low-level compiler functions
+ */
+
+struct sieve_ast *
+sieve_parse(struct sieve_script *script, struct sieve_error_handler *ehandler,
+ enum sieve_error *error_r)
+{
+ struct sieve_parser *parser;
+ struct sieve_ast *ast = NULL;
+
+ /* Parse */
+ parser = sieve_parser_create(script, ehandler, error_r);
+ if (parser == NULL)
+ return NULL;
+
+ if (!sieve_parser_run(parser, &ast))
+ ast = NULL;
+ else
+ sieve_ast_ref(ast);
+
+ sieve_parser_free(&parser);
+
+ if (error_r != NULL) {
+ if (ast == NULL)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ else
+ *error_r = SIEVE_ERROR_NONE;
+ }
+ return ast;
+}
+
+bool sieve_validate(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+ bool result = TRUE;
+ struct sieve_validator *validator =
+ sieve_validator_create(ast, ehandler, flags);
+
+ if (!sieve_validator_run(validator))
+ result = FALSE;
+
+ sieve_validator_free(&validator);
+
+ if (error_r != NULL) {
+ if (!result)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ else
+ *error_r = SIEVE_ERROR_NONE;
+ }
+ return result;
+}
+
+static struct sieve_binary *
+sieve_generate(struct sieve_ast *ast, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+ struct sieve_generator *generator =
+ sieve_generator_create(ast, ehandler, flags);
+ struct sieve_binary *sbin = NULL;
+
+ sbin = sieve_generator_run(generator, NULL);
+
+ sieve_generator_free(&generator);
+
+ if (error_r != NULL) {
+ if (sbin == NULL)
+ *error_r = SIEVE_ERROR_NOT_VALID;
+ else
+ *error_r = SIEVE_ERROR_NONE;
+ }
+ return sbin;
+}
+
+/*
+ * Sieve compilation
+ */
+
+struct sieve_binary *
+sieve_compile_script(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags,
+ enum sieve_error *error_r)
+{
+ struct sieve_ast *ast;
+ struct sieve_binary *sbin;
+ enum sieve_error error, *errorp;
+
+ if (error_r != NULL)
+ errorp = error_r;
+ else
+ errorp = &error;
+ *errorp = SIEVE_ERROR_NONE;
+
+ /* Parse */
+ ast = sieve_parse(script, ehandler, errorp);
+ if (ast == NULL) {
+ switch (*errorp) {
+ case SIEVE_ERROR_NOT_FOUND:
+ if (error_r == NULL) {
+ sieve_error(ehandler, sieve_script_name(script),
+ "script not found");
+ }
+ break;
+ default:
+ sieve_error(ehandler, sieve_script_name(script),
+ "parse failed");
+ }
+ return NULL;
+ }
+
+ /* Validate */
+ if (!sieve_validate(ast, ehandler, flags, errorp)) {
+ sieve_error(ehandler, sieve_script_name(script),
+ "validation failed");
+
+ sieve_ast_unref(&ast);
+ return NULL;
+ }
+
+ /* Generate */
+ sbin = sieve_generate(ast, ehandler, flags, errorp);
+ if (sbin == NULL) {
+ sieve_error(ehandler, sieve_script_name(script),
+ "code generation failed");
+ sieve_ast_unref(&ast);
+ return NULL;
+ }
+
+ /* Cleanup */
+ sieve_ast_unref(&ast);
+ return sbin;
+}
+
+struct sieve_binary *
+sieve_compile(struct sieve_instance *svinst, const char *script_location,
+ const char *script_name, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+ struct sieve_binary *sbin;
+ enum sieve_error error;
+
+ script = sieve_script_create_open(svinst, script_location,
+ script_name, &error);
+ if (script == NULL) {
+ if (error_r != NULL)
+ *error_r = error;
+ switch (error) {
+ case SIEVE_ERROR_NOT_FOUND:
+ sieve_error(ehandler, script_name, "script not found");
+ break;
+ default:
+ sieve_internal_error(ehandler, script_name,
+ "failed to open script");
+ }
+ return NULL;
+ }
+
+ sbin = sieve_compile_script(script, ehandler, flags, error_r);
+ if (sbin != NULL) {
+ e_debug(svinst->event,
+ "Script `%s' from %s successfully compiled",
+ sieve_script_name(script),
+ sieve_script_location(script));
+ }
+
+ sieve_script_unref(&script);
+ return sbin;
+}
+
+/*
+ * Sieve runtime
+ */
+
+static int
+sieve_run(struct sieve_binary *sbin, struct sieve_result *result,
+ struct sieve_execute_env *eenv, struct sieve_error_handler *ehandler)
+{
+ struct sieve_interpreter *interp;
+ int ret = 0;
+
+ /* Create the interpreter */
+ interp = sieve_interpreter_create(sbin, NULL, eenv, ehandler);
+ if (interp == NULL)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ /* Run the interpreter */
+ ret = sieve_interpreter_run(interp, result);
+
+ /* Free the interpreter */
+ sieve_interpreter_free(&interp);
+
+ return ret;
+}
+
+/*
+ * Reading/writing sieve binaries
+ */
+
+struct sieve_binary *
+sieve_load(struct sieve_instance *svinst, const char *bin_path,
+ enum sieve_error *error_r)
+{
+ return sieve_binary_open(svinst, bin_path, NULL, error_r);
+}
+
+static struct sieve_binary *
+sieve_open_script_real(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags,
+ enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = sieve_script_svinst(script);
+ struct sieve_resource_usage rusage;
+ struct sieve_binary *sbin;
+ enum sieve_error error;
+ const char *errorstr = NULL;
+ int ret;
+
+ if (error_r == NULL)
+ error_r = &error;
+
+ sieve_resource_usage_init(&rusage);
+
+ /* Try to open the matching binary */
+ sbin = sieve_script_binary_load(script, error_r);
+ if (sbin != NULL) {
+ sieve_binary_get_resource_usage(sbin, &rusage);
+
+ /* Ok, it exists; now let's see if it is up to date */
+ if (!sieve_resource_usage_is_excessive(svinst, &rusage) &&
+ !sieve_binary_up_to_date(sbin, flags)) {
+ /* Not up to date */
+ e_debug(svinst->event,
+ "Script binary %s is not up-to-date",
+ sieve_binary_path(sbin));
+ sieve_binary_close(&sbin);
+ }
+ }
+
+ /* If the binary does not exist or is not up-to-date, we need
+ * to (re-)compile.
+ */
+ if (sbin != NULL) {
+ e_debug(svinst->event,
+ "Script binary %s successfully loaded",
+ sieve_binary_path(sbin));
+ } else {
+ sbin = sieve_compile_script(script, ehandler, flags, error_r);
+ if (sbin == NULL)
+ return NULL;
+
+ e_debug(svinst->event,
+ "Script `%s' from %s successfully compiled",
+ sieve_script_name(script),
+ sieve_script_location(script));
+
+ sieve_binary_set_resource_usage(sbin, &rusage);
+ }
+
+ /* Check whether binary can be executed. */
+ ret = sieve_binary_check_executable(sbin, error_r, &errorstr);
+ if (ret <= 0) {
+ const char *path = sieve_binary_path(sbin);
+
+ if (path != NULL) {
+ e_debug(svinst->event,
+ "Script binary %s cannot be executed",
+ path);
+ } else {
+ e_debug(svinst->event,
+ "Script binary from %s cannot be executed",
+ sieve_binary_source(sbin));
+ }
+ if (ret < 0) {
+ sieve_internal_error(ehandler,
+ sieve_script_name(script),
+ "failed to open script");
+ } else {
+ sieve_error(ehandler, sieve_script_name(script),
+ "%s", errorstr);
+ }
+ sieve_binary_close(&sbin);
+ }
+
+ return sbin;
+}
+
+struct sieve_binary *
+sieve_open_script(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+ struct sieve_binary *sbin;
+
+ T_BEGIN {
+ sbin = sieve_open_script_real(script, ehandler, flags, error_r);
+ } T_END;
+
+ return sbin;
+}
+
+struct sieve_binary *
+sieve_open(struct sieve_instance *svinst, const char *script_location,
+ const char *script_name, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+{
+ struct sieve_script *script;
+ struct sieve_binary *sbin;
+ enum sieve_error error;
+
+ /* First open the scriptfile itself */
+ script = sieve_script_create_open(svinst, script_location,
+ script_name, &error);
+ if (script == NULL) {
+ /* Failed */
+ if (error_r != NULL)
+ *error_r = error;
+ switch (error) {
+ case SIEVE_ERROR_NOT_FOUND:
+ sieve_error(ehandler, script_name, "script not found");
+ break;
+ default:
+ sieve_internal_error(ehandler, script_name,
+ "failed to open script");
+ }
+ return NULL;
+ }
+
+ sbin = sieve_open_script(script, ehandler, flags, error_r);
+
+ /* Drop script reference, if sbin != NULL it holds a reference of its own.
+ * Otherwise the script object is freed here.
+ */
+ sieve_script_unref(&script);
+
+ return sbin;
+}
+
+const char *sieve_get_source(struct sieve_binary *sbin)
+{
+ return sieve_binary_source(sbin);
+}
+
+bool sieve_is_loaded(struct sieve_binary *sbin)
+{
+ return sieve_binary_loaded(sbin);
+}
+
+int sieve_save_as(struct sieve_binary *sbin, const char *bin_path, bool update,
+ mode_t save_mode, enum sieve_error *error_r)
+{
+ if (bin_path == NULL)
+ return sieve_save(sbin, update, error_r);
+
+ return sieve_binary_save(sbin, bin_path, update, save_mode, error_r);
+}
+
+int sieve_save(struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r)
+{
+ struct sieve_script *script = sieve_binary_script(sbin);
+
+ if (script == NULL)
+ return sieve_binary_save(sbin, NULL, update, 0600, error_r);
+
+ return sieve_script_binary_save(script, sbin, update, error_r);
+}
+
+bool sieve_record_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage)
+{
+ return sieve_binary_record_resource_usage(sbin, rusage);
+}
+
+int sieve_check_executable(struct sieve_binary *sbin,
+ enum sieve_error *error_r,
+ const char **client_error_r)
+{
+ return sieve_binary_check_executable(sbin, error_r, client_error_r);
+}
+
+void sieve_close(struct sieve_binary **_sbin)
+{
+ sieve_binary_close(_sbin);
+}
+
+/*
+ * Debugging
+ */
+
+void sieve_dump(struct sieve_binary *sbin, struct ostream *stream, bool verbose)
+{
+ struct sieve_binary_dumper *dumpr = sieve_binary_dumper_create(sbin);
+
+ sieve_binary_dumper_run(dumpr, stream, verbose);
+
+ sieve_binary_dumper_free(&dumpr);
+}
+
+void sieve_hexdump(struct sieve_binary *sbin, struct ostream *stream)
+{
+ struct sieve_binary_dumper *dumpr = sieve_binary_dumper_create(sbin);
+
+ sieve_binary_dumper_hexdump(dumpr, stream);
+
+ sieve_binary_dumper_free(&dumpr);
+}
+
+int sieve_test(struct sieve_binary *sbin,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct sieve_error_handler *ehandler, struct ostream *stream,
+ enum sieve_execute_flags flags)
+{
+ struct sieve_instance *svinst = sieve_binary_svinst(sbin);
+ struct sieve_result *result;
+ struct sieve_execute_env eenv;
+ pool_t pool;
+ int ret;
+
+ pool = pool_alloconly_create("sieve execution", 4096);
+ sieve_execute_init(&eenv, svinst, pool, msgdata, senv, flags);
+
+ /* Create result object */
+ result = sieve_result_create(svinst, pool, &eenv);
+
+ /* Run the script */
+ ret = sieve_run(sbin, result, &eenv, ehandler);
+
+ /* Print result if successful */
+ if (ret > 0) {
+ ret = (sieve_result_print(result, senv, stream, NULL) ?
+ SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
+ }
+
+ /* Cleanup */
+ if (result != NULL)
+ sieve_result_unref(&result);
+ sieve_execute_deinit(&eenv);
+ pool_unref(&pool);
+
+ return ret;
+}
+
+/*
+ * Script execution
+ */
+
+int sieve_script_env_init(struct sieve_script_env *senv, struct mail_user *user,
+ const char **error_r)
+{
+ const struct message_address *postmaster;
+ const char *error;
+
+ if (!mail_user_get_postmaster_address(user, &postmaster, &error)) {
+ *error_r = t_strdup_printf(
+ "Invalid postmaster_address: %s", error);
+ return -1;
+ }
+
+ i_zero(senv);
+ senv->user = user;
+ senv->postmaster_address = postmaster;
+ return 0;
+}
+
+int sieve_execute(struct sieve_binary *sbin,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags)
+{
+ struct sieve_instance *svinst = sieve_binary_svinst(sbin);
+ struct sieve_result *result = NULL;
+ struct sieve_result_execution *rexec;
+ struct sieve_execute_env eenv;
+ pool_t pool;
+ int ret;
+
+ pool = pool_alloconly_create("sieve execution", 4096);
+ sieve_execute_init(&eenv, svinst, pool, msgdata, senv, flags);
+
+ /* Create result object */
+ result = sieve_result_create(svinst, pool, &eenv);
+
+ /* Run the script */
+ ret = sieve_run(sbin, result, &eenv, exec_ehandler);
+
+ rexec = sieve_result_execution_create(result, pool);
+
+ /* Evaluate status and execute the result:
+ Strange situations, e.g. currupt binaries, must be handled by the
+ caller. In that case no implicit keep is attempted, because the
+ situation may be resolved.
+ */
+ ret = sieve_result_execute(rexec, ret, TRUE, action_ehandler, NULL);
+
+ sieve_result_execution_destroy(&rexec);
+
+ /* Cleanup */
+ if (result != NULL)
+ sieve_result_unref(&result);
+ sieve_execute_finish(&eenv, ret);
+ sieve_execute_deinit(&eenv);
+ pool_unref(&pool);
+
+ return ret;
+}
+
+/*
+ * Multiscript support
+ */
+
+struct sieve_multiscript {
+ pool_t pool;
+ struct sieve_execute_env exec_env;
+ struct sieve_result *result;
+ struct sieve_result_execution *rexec;
+ struct event *event;
+
+ int status;
+ bool keep;
+
+ struct ostream *teststream;
+
+ bool active:1;
+ bool discard_handled:1;
+};
+
+struct sieve_multiscript *
+sieve_multiscript_start_execute(struct sieve_instance *svinst,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv)
+{
+ pool_t pool;
+ struct sieve_result *result;
+ struct sieve_multiscript *mscript;
+
+ pool = pool_alloconly_create("sieve execution", 4096);
+ mscript = p_new(pool, struct sieve_multiscript, 1);
+ mscript->pool = pool;
+ sieve_execute_init(&mscript->exec_env, svinst, pool, msgdata, senv, 0);
+
+ mscript->event = event_create(mscript->exec_env.event);
+ event_set_append_log_prefix(mscript->event, "multi-script: ");
+
+ result = sieve_result_create(svinst, pool, &mscript->exec_env);
+ sieve_result_set_keep_action(result, NULL, NULL);
+ mscript->result = result;
+
+ mscript->rexec = sieve_result_execution_create(result, pool);
+
+ mscript->status = SIEVE_EXEC_OK;
+ mscript->active = TRUE;
+ mscript->keep = TRUE;
+
+ e_debug(mscript->event, "Start execute sequence");
+
+ return mscript;
+}
+
+static void sieve_multiscript_destroy(struct sieve_multiscript **_mscript)
+{
+ struct sieve_multiscript *mscript = *_mscript;
+
+ if (mscript == NULL)
+ return;
+ *_mscript = NULL;
+
+ e_debug(mscript->event, "Destroy");
+
+ event_unref(&mscript->event);
+
+ sieve_result_execution_destroy(&mscript->rexec);
+ sieve_result_unref(&mscript->result);
+ sieve_execute_deinit(&mscript->exec_env);
+ pool_unref(&mscript->pool);
+}
+
+struct sieve_multiscript *
+sieve_multiscript_start_test(struct sieve_instance *svinst,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct ostream *stream)
+{
+ struct sieve_multiscript *mscript =
+ sieve_multiscript_start_execute(svinst, msgdata, senv);
+
+ mscript->teststream = stream;
+
+ return mscript;
+}
+
+static void
+sieve_multiscript_test(struct sieve_multiscript *mscript)
+{
+ const struct sieve_script_env *senv = mscript->exec_env.scriptenv;
+
+ e_debug(mscript->event, "Test result");
+
+ if (mscript->status > 0) {
+ mscript->status =
+ (sieve_result_print(mscript->result, senv,
+ mscript->teststream,
+ &mscript->keep) ?
+ SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE);
+ } else {
+ mscript->keep = TRUE;
+ }
+
+ sieve_result_mark_executed(mscript->result);
+}
+
+static void
+sieve_multiscript_execute(struct sieve_multiscript *mscript,
+ struct sieve_error_handler *ehandler,
+ enum sieve_execute_flags flags)
+{
+ e_debug(mscript->event, "Execute result");
+
+ mscript->exec_env.flags = flags;
+
+ if (mscript->status > 0) {
+ mscript->status = sieve_result_execute(mscript->rexec,
+ SIEVE_EXEC_OK, FALSE,
+ ehandler,
+ &mscript->keep);
+ }
+}
+
+bool sieve_multiscript_run(struct sieve_multiscript *mscript,
+ struct sieve_binary *sbin,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags)
+{
+ if (!mscript->active) {
+ e_debug(mscript->event, "Sequence ended");
+ return FALSE;
+ }
+
+ e_debug(mscript->event, "Run script `%s'", sieve_binary_source(sbin));
+
+ /* Run the script */
+ mscript->exec_env.flags = flags;
+ mscript->status = sieve_run(sbin, mscript->result, &mscript->exec_env,
+ exec_ehandler);
+
+ if (mscript->status >= 0) {
+ mscript->keep = FALSE;
+
+ if (mscript->teststream != NULL)
+ sieve_multiscript_test(mscript);
+ else {
+ sieve_multiscript_execute(mscript, action_ehandler,
+ flags);
+ }
+ if (!mscript->keep)
+ mscript->active = FALSE;
+ }
+
+ if (!mscript->active || mscript->status <= 0) {
+ e_debug(mscript->event, "Sequence ended");
+ mscript->active = FALSE;
+ return FALSE;
+ }
+
+ e_debug(mscript->event, "Sequence active");
+ return TRUE;
+}
+
+bool sieve_multiscript_will_discard(struct sieve_multiscript *mscript)
+{
+ return (!mscript->active && mscript->status == SIEVE_EXEC_OK &&
+ !sieve_result_executed_delivery(mscript->rexec));
+}
+
+void sieve_multiscript_run_discard(struct sieve_multiscript *mscript,
+ struct sieve_binary *sbin,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags)
+{
+ if (!sieve_multiscript_will_discard(mscript)) {
+ e_debug(mscript->event, "Not running discard script");
+ return;
+ }
+ i_assert(!mscript->discard_handled);
+
+ e_debug(mscript->event, "Run discard script `%s'",
+ sieve_binary_source(sbin));
+
+ sieve_result_set_keep_action(mscript->result, NULL, &act_store);
+
+ /* Run the discard script */
+ flags |= SIEVE_EXECUTE_FLAG_DEFER_KEEP;
+ mscript->exec_env.flags = flags;
+ mscript->status = sieve_run(sbin, mscript->result, &mscript->exec_env,
+ exec_ehandler);
+
+ if (mscript->status >= 0) {
+ mscript->keep = FALSE;
+
+ if (mscript->teststream != NULL)
+ sieve_multiscript_test(mscript);
+ else {
+ sieve_multiscript_execute(mscript, action_ehandler,
+ flags);
+ }
+ if (mscript->status == SIEVE_EXEC_FAILURE)
+ mscript->status = SIEVE_EXEC_KEEP_FAILED;
+ mscript->active = FALSE;
+ }
+
+ mscript->discard_handled = TRUE;
+}
+
+int sieve_multiscript_status(struct sieve_multiscript *mscript)
+{
+ return mscript->status;
+}
+
+int sieve_multiscript_finish(struct sieve_multiscript **_mscript,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags, int status)
+{
+ struct sieve_multiscript *mscript = *_mscript;
+
+ if (mscript == NULL)
+ return SIEVE_EXEC_OK;
+ *_mscript = NULL;
+
+ switch (status) {
+ case SIEVE_EXEC_OK:
+ status = mscript->status;
+ break;
+ case SIEVE_EXEC_TEMP_FAILURE:
+ break;
+ case SIEVE_EXEC_BIN_CORRUPT:
+ case SIEVE_EXEC_FAILURE:
+ case SIEVE_EXEC_KEEP_FAILED:
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ if (mscript->status == SIEVE_EXEC_TEMP_FAILURE)
+ status = mscript->status;
+ break;
+ }
+
+ e_debug(mscript->event, "Finishing sequence (status=%s)",
+ sieve_execution_exitcode_to_str(status));
+
+ mscript->exec_env.flags = flags;
+ sieve_result_set_keep_action(mscript->result, NULL, &act_store);
+
+ mscript->keep = FALSE;
+ if (mscript->teststream != NULL)
+ mscript->keep = TRUE;
+ else {
+ status = sieve_result_execute(
+ mscript->rexec, status, TRUE, action_ehandler,
+ &mscript->keep);
+ }
+
+ e_debug(mscript->event, "Sequence finished (status=%s, keep=%s)",
+ sieve_execution_exitcode_to_str(status),
+ (mscript->keep ? "yes" : "no"));
+
+ sieve_execute_finish(&mscript->exec_env, status);
+
+ /* Cleanup */
+ sieve_multiscript_destroy(&mscript);
+
+ return status;
+}
+
+/*
+ * Configured Limits
+ */
+
+unsigned int sieve_max_redirects(struct sieve_instance *svinst)
+{
+ return svinst->max_redirects;
+}
+
+unsigned int sieve_max_actions(struct sieve_instance *svinst)
+{
+ return svinst->max_actions;
+}
+
+size_t sieve_max_script_size(struct sieve_instance *svinst)
+{
+ return svinst->max_script_size;
+}
+
+/*
+ * User log
+ */
+
+const char *
+sieve_user_get_log_path(struct sieve_instance *svinst,
+ struct sieve_script *user_script)
+{
+ const char *log_path = NULL;
+
+ /* Determine user log file path */
+ log_path = sieve_setting_get(svinst, "sieve_user_log");
+ if (log_path == NULL) {
+ const char *path;
+
+ if (user_script == NULL ||
+ (path = sieve_file_script_get_path(user_script)) == NULL) {
+ /* Default */
+ if (svinst->home_dir != NULL) {
+ log_path = t_strconcat(
+ svinst->home_dir, "/.dovecot.sieve.log",
+ NULL);
+ }
+ } else {
+ /* Use script file as a base (legacy behavior) */
+ log_path = t_strconcat(path, ".log", NULL);
+ }
+ } else if (svinst->home_dir != NULL) {
+ /* Expand home dir if necessary */
+ if (log_path[0] == '~') {
+ log_path = home_expand_tilde(log_path,
+ svinst->home_dir);
+ } else if (log_path[0] != '/') {
+ log_path = t_strconcat(svinst->home_dir, "/",
+ log_path, NULL);
+ }
+ }
+ return log_path;
+}
+
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log {
+ struct ostream *output;
+};
+
+int sieve_trace_log_create(struct sieve_instance *svinst, const char *path,
+ struct sieve_trace_log **trace_log_r)
+{
+ struct sieve_trace_log *trace_log;
+ struct ostream *output;
+ int fd;
+
+ *trace_log_r = NULL;
+
+ if (path == NULL)
+ output = o_stream_create_fd(1, 0);
+ else {
+ fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0600);
+ if (fd == -1) {
+ e_error(svinst->event, "trace: "
+ "creat(%s) failed: %m", path);
+ return -1;
+ }
+ output = o_stream_create_fd_autoclose(&fd, 0);
+ o_stream_set_name(output, path);
+ }
+
+ trace_log = i_new(struct sieve_trace_log, 1);
+ trace_log->output = output;
+
+ *trace_log_r = trace_log;
+ return 0;
+}
+
+int sieve_trace_log_create_dir(struct sieve_instance *svinst, const char *dir,
+ struct sieve_trace_log **trace_log_r)
+{
+ static unsigned int counter = 0;
+ const char *timestamp, *prefix;
+ struct stat st;
+
+ *trace_log_r = NULL;
+
+ if (stat(dir, &st) < 0) {
+ if (errno != ENOENT && errno != EACCES) {
+ e_error(svinst->event, "trace: "
+ "stat(%s) failed: %m", dir);
+ }
+ return -1;
+ }
+
+ timestamp = t_strflocaltime("%Y%m%d-%H%M%S", ioloop_time);
+
+ counter++;
+
+ prefix = t_strdup_printf("%s/%s.%s.%u.trace",
+ dir, timestamp, my_pid, counter);
+ return sieve_trace_log_create(svinst, prefix, trace_log_r);
+}
+
+int sieve_trace_log_open(struct sieve_instance *svinst,
+ struct sieve_trace_log **trace_log_r)
+{
+ const char *trace_dir =
+ sieve_setting_get(svinst, "sieve_trace_dir");
+
+ *trace_log_r = NULL;
+ if (trace_dir == NULL)
+ return -1;
+
+ if (svinst->home_dir != NULL) {
+ /* Expand home dir if necessary */
+ if (trace_dir[0] == '~') {
+ trace_dir = home_expand_tilde(trace_dir,
+ svinst->home_dir);
+ } else if (trace_dir[0] != '/') {
+ trace_dir = t_strconcat(svinst->home_dir, "/",
+ trace_dir, NULL);
+ }
+ }
+
+ return sieve_trace_log_create_dir(svinst, trace_dir, trace_log_r);
+}
+
+void sieve_trace_log_write_line(struct sieve_trace_log *trace_log,
+ const string_t *line)
+{
+ struct const_iovec iov[2];
+
+ if (line == NULL) {
+ o_stream_nsend_str(trace_log->output, "\n");
+ return;
+ }
+
+ memset(iov, 0, sizeof(iov));
+ iov[0].iov_base = str_data(line);
+ iov[0].iov_len = str_len(line);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ o_stream_nsendv(trace_log->output, iov, 2);
+}
+
+void sieve_trace_log_printf(struct sieve_trace_log *trace_log,
+ const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ T_BEGIN {
+ o_stream_nsend_str(trace_log->output,
+ t_strdup_vprintf(fmt, args));
+ } T_END;
+ va_end(args);
+}
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log)
+{
+ struct sieve_trace_log *trace_log = *_trace_log;
+
+ *_trace_log = NULL;
+
+ if (o_stream_finish(trace_log->output) < 0) {
+ i_error("write(%s) failed: %s",
+ o_stream_get_name(trace_log->output),
+ o_stream_get_error(trace_log->output));
+ }
+ o_stream_destroy(&trace_log->output);
+ i_free(trace_log);
+}
+
+int sieve_trace_config_get(struct sieve_instance *svinst,
+ struct sieve_trace_config *tr_config)
+{
+ const char *tr_level =
+ sieve_setting_get(svinst, "sieve_trace_level");
+ bool tr_debug, tr_addresses;
+
+ i_zero(tr_config);
+
+ if (tr_level == NULL || *tr_level == '\0' ||
+ strcasecmp(tr_level, "none") == 0)
+ return -1;
+
+ if (strcasecmp(tr_level, "actions") == 0)
+ tr_config->level = SIEVE_TRLVL_ACTIONS;
+ else if (strcasecmp(tr_level, "commands") == 0)
+ tr_config->level = SIEVE_TRLVL_COMMANDS;
+ else if (strcasecmp(tr_level, "tests") == 0)
+ tr_config->level = SIEVE_TRLVL_TESTS;
+ else if (strcasecmp(tr_level, "matching") == 0)
+ tr_config->level = SIEVE_TRLVL_MATCHING;
+ else {
+ e_error(svinst->event, "Unknown trace level: %s", tr_level);
+ return -1;
+ }
+
+ tr_debug = FALSE;
+ (void)sieve_setting_get_bool_value(svinst, "sieve_trace_debug",
+ &tr_debug);
+ tr_addresses = FALSE;
+ (void)sieve_setting_get_bool_value(svinst, "sieve_trace_addresses",
+ &tr_addresses);
+
+ if (tr_debug)
+ tr_config->flags |= SIEVE_TRFLG_DEBUG;
+ if (tr_addresses)
+ tr_config->flags |= SIEVE_TRFLG_ADDRESSES;
+ return 0;
+}
+
+/*
+ * Execution exit codes
+ */
+
+const char *sieve_execution_exitcode_to_str(int code)
+{
+ switch (code) {
+ case SIEVE_EXEC_OK:
+ return "ok";
+ case SIEVE_EXEC_FAILURE:
+ return "failure";
+ case SIEVE_EXEC_TEMP_FAILURE:
+ return "temporary_failure";
+ case SIEVE_EXEC_BIN_CORRUPT:
+ return "binary_corrupt";
+ case SIEVE_EXEC_KEEP_FAILED:
+ return "keep_failed";
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ return "resource_limit";
+ }
+ i_unreached();
+}
+
+/*
+ * User e-mail address
+ */
+
+const struct smtp_address *sieve_get_user_email(struct sieve_instance *svinst)
+{
+ struct smtp_address *address;
+ const char *username = svinst->username;
+
+ if (svinst->user_email_implicit != NULL)
+ return svinst->user_email_implicit;
+ if (svinst->user_email != NULL)
+ return svinst->user_email;
+
+ if (smtp_address_parse_mailbox(svinst->pool, username, 0,
+ &address, NULL) >= 0) {
+ svinst->user_email_implicit = address;
+ return svinst->user_email_implicit;
+ }
+
+ if (svinst->domainname != NULL) {
+ svinst->user_email_implicit = smtp_address_create(
+ svinst->pool, username, svinst->domainname);
+ return svinst->user_email_implicit;
+ }
+ return NULL;
+}
+
+/*
+ * Postmaster address
+ */
+
+const struct message_address *
+sieve_get_postmaster(const struct sieve_script_env *senv)
+{
+ i_assert(senv->postmaster_address != NULL);
+ return senv->postmaster_address;
+}
+
+const struct smtp_address *
+sieve_get_postmaster_smtp(const struct sieve_script_env *senv)
+{
+ struct smtp_address *addr;
+ int ret;
+
+ ret = smtp_address_create_from_msg_temp(
+ sieve_get_postmaster(senv), &addr);
+ i_assert(ret >= 0);
+ return addr;
+}
+
+const char *sieve_get_postmaster_address(const struct sieve_script_env *senv)
+{
+ const struct message_address *postmaster =
+ sieve_get_postmaster(senv);
+ string_t *addr = t_str_new(256);
+
+ message_address_write(addr, postmaster);
+ return str_c(addr);
+}
+
+/*
+ * Resource usage
+ */
+
+void sieve_resource_usage_init(struct sieve_resource_usage *rusage_r)
+{
+ i_zero(rusage_r);
+}
+
+void sieve_resource_usage_add(struct sieve_resource_usage *dst,
+ const struct sieve_resource_usage *src)
+{
+ if ((UINT_MAX - dst->cpu_time_msecs) < src->cpu_time_msecs)
+ dst->cpu_time_msecs = UINT_MAX;
+ else
+ dst->cpu_time_msecs += src->cpu_time_msecs;
+}
+
+bool sieve_resource_usage_is_high(struct sieve_instance *svinst ATTR_UNUSED,
+ const struct sieve_resource_usage *rusage)
+{
+ return (rusage->cpu_time_msecs > SIEVE_HIGH_CPU_TIME_MSECS);
+}
+
+bool sieve_resource_usage_is_excessive(
+ struct sieve_instance *svinst,
+ const struct sieve_resource_usage *rusage)
+{
+ i_assert(svinst->max_cpu_time_secs <= (UINT_MAX / 1000));
+ if (svinst->max_cpu_time_secs == 0)
+ return FALSE;
+ return (rusage->cpu_time_msecs > (svinst->max_cpu_time_secs * 1000));
+}
+
+const char *
+sieve_resource_usage_get_summary(const struct sieve_resource_usage *rusage)
+{
+ if (rusage->cpu_time_msecs == 0)
+ return "no usage recorded";
+
+ return t_strdup_printf("cpu time = %u ms", rusage->cpu_time_msecs);
+}
diff --git a/pigeonhole/src/lib-sieve/sieve.h b/pigeonhole/src/lib-sieve/sieve.h
new file mode 100644
index 0000000..66a6d12
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/sieve.h
@@ -0,0 +1,261 @@
+#ifndef SIEVE_H
+#define SIEVE_H
+
+struct sieve_script;
+struct sieve_binary;
+
+#include "sieve-config.h"
+#include "sieve-types.h"
+#include "sieve-error.h"
+
+/*
+ * Main Sieve library interface
+ */
+
+/* Initialize the sieve engine. Must be called before any sieve functionality is
+ used. */
+struct sieve_instance *
+sieve_init(const struct sieve_environment *env,
+ const struct sieve_callbacks *callbacks, void *context, bool debug);
+
+/* Free all memory allocated by the sieve engine. */
+void sieve_deinit(struct sieve_instance **_svinst);
+
+/* Get capability string for a particular extension. */
+const char *
+sieve_get_capabilities(struct sieve_instance *svinst, const char *name);
+
+/* Set the supported extensions. The provided string is parsed into a list
+ of extensions that are to be enabled/disabled. */
+void sieve_set_extensions(struct sieve_instance *svinst,
+ const char *extensions);
+
+
+/* Get top-level event for this Sieve instance. */
+struct event *sieve_get_event(struct sieve_instance *svinst) ATTR_PURE;
+
+/*
+ * Script compilation
+ */
+
+/* Compile a Sieve script from a Sieve script object. Returns Sieve binary upon
+ success and NULL upon failure. */
+struct sieve_binary *
+sieve_compile_script(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+ ATTR_NULL(2, 4);
+
+/* Compile a Sieve script from a Sieve script location string. Returns Sieve
+ binary upon success and NULL upon failure. The provided script_name is used
+ for the internally created Sieve script object. */
+struct sieve_binary *
+sieve_compile(struct sieve_instance *svinst, const char *script_location,
+ const char *script_name, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r)
+ ATTR_NULL(3, 4, 6);
+
+/*
+ * Reading/writing Sieve binaries
+ */
+
+/* Loads the sieve binary indicated by the provided path. */
+struct sieve_binary *
+sieve_load(struct sieve_instance *svinst, const char *bin_path,
+ enum sieve_error *error_r);
+/* First tries to open the binary version of the specified script and if it does
+ not exist or if it contains errors, the script is (re-)compiled. Note that
+ errors in the bytecode are caught only at runtime.
+ */
+struct sieve_binary *
+sieve_open_script(struct sieve_script *script,
+ struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r);
+/* First tries to open the binary version of the specified script and if it does
+ not exist or if it contains errors, the script is (re-)compiled. Note that
+ errors in the bytecode are caught only at runtime.
+ */
+struct sieve_binary *
+sieve_open(struct sieve_instance *svinst, const char *script_location,
+ const char *script_name, struct sieve_error_handler *ehandler,
+ enum sieve_compile_flags flags, enum sieve_error *error_r);
+
+/* Record resource usage in the binary cumulatively. The binary is disabled when
+ resource limits are exceeded within a configured timeout. Returns FALSE when
+ resource limits are exceeded. */
+bool ATTR_NOWARN_UNUSED_RESULT
+sieve_record_resource_usage(struct sieve_binary *sbin,
+ const struct sieve_resource_usage *rusage)
+ ATTR_NULL(1);
+
+/* Check whether Sieve binary is (still) executable. Returns 1 if all is OK,
+ 0 when an error occurred, and -1 when the error is internal. Sets the Sieve
+ error code in error_r and a user error message in client_error_r when the
+ error is not internal. */
+int sieve_check_executable(struct sieve_binary *sbin,
+ enum sieve_error *error_r,
+ const char **client_error_r);
+
+/* Saves the binary as the file indicated by the path parameter. This function
+ will not write the binary to disk when the provided binary object was loaded
+ earlier from the indicated bin_path, unless update is TRUE.
+ */
+int sieve_save_as(struct sieve_binary *sbin, const char *bin_path, bool update,
+ mode_t save_mode, enum sieve_error *error_r);
+
+/* Saves the binary to the default location. This function will not overwrite
+ the binary on disk when the provided binary object was loaded earlier from
+ the default location, unless update is TRUE.
+ */
+int sieve_save(struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r);
+
+/* Closes a compiled/opened sieve binary. */
+void sieve_close(struct sieve_binary **_sbin);
+
+/* Obtains the path the binary was compiled or loaded from. */
+const char *sieve_get_source(struct sieve_binary *sbin);
+/* Indicates whether the binary was loaded from a pre-compiled file. */
+bool sieve_is_loaded(struct sieve_binary *sbin);
+
+/*
+ * Debugging
+ */
+
+/* Dumps the byte code in human-readable form to the specified ostream. */
+void sieve_dump(struct sieve_binary *sbin,
+ struct ostream *stream, bool verbose);
+/* Dumps the byte code in hexdump form to the specified ostream. */
+void sieve_hexdump(struct sieve_binary *sbin, struct ostream *stream);
+
+/* Executes the bytecode, but only prints the result to the given stream. */
+int sieve_test(struct sieve_binary *sbin,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct sieve_error_handler *ehandler, struct ostream *stream,
+ enum sieve_execute_flags flags);
+
+/*
+ * Script execution
+ */
+
+/* Initializes the scirpt environment from the given mail_user. */
+int sieve_script_env_init(struct sieve_script_env *senv, struct mail_user *user,
+ const char **error_r);
+
+/* Executes the binary, including the result. */
+int sieve_execute(struct sieve_binary *sbin,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags);
+
+/*
+ * Multiscript support
+ */
+
+struct sieve_multiscript;
+
+struct sieve_multiscript *
+sieve_multiscript_start_execute(struct sieve_instance *svinst,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv);
+struct sieve_multiscript *
+sieve_multiscript_start_test(struct sieve_instance *svinst,
+ const struct sieve_message_data *msgdata,
+ const struct sieve_script_env *senv,
+ struct ostream *stream);
+
+bool sieve_multiscript_run(struct sieve_multiscript *mscript,
+ struct sieve_binary *sbin,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags);
+
+bool sieve_multiscript_will_discard(struct sieve_multiscript *mscript);
+void sieve_multiscript_run_discard(struct sieve_multiscript *mscript,
+ struct sieve_binary *sbin,
+ struct sieve_error_handler *exec_ehandler,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags);
+
+int sieve_multiscript_status(struct sieve_multiscript *mscript);
+
+int sieve_multiscript_finish(struct sieve_multiscript **_mscript,
+ struct sieve_error_handler *action_ehandler,
+ enum sieve_execute_flags flags, int status);
+
+/*
+ * Configured limits
+ */
+
+unsigned int sieve_max_redirects(struct sieve_instance *svinst);
+unsigned int sieve_max_actions(struct sieve_instance *svinst);
+size_t sieve_max_script_size(struct sieve_instance *svinst);
+
+/*
+ * User log
+ */
+
+const char *sieve_user_get_log_path(struct sieve_instance *svinst,
+ struct sieve_script *user_script)
+ ATTR_NULL(2);
+
+/*
+ * Script trace log
+ */
+
+struct sieve_trace_log;
+
+int sieve_trace_log_create(struct sieve_instance *svinst, const char *path,
+ struct sieve_trace_log **trace_log_r) ATTR_NULL(2);
+int sieve_trace_log_create_dir(struct sieve_instance *svinst, const char *dir,
+ struct sieve_trace_log **trace_log_r)
+ ATTR_NULL(3);
+
+int sieve_trace_log_open(struct sieve_instance *svinst,
+ struct sieve_trace_log **trace_log_r) ATTR_NULL(2);
+
+void sieve_trace_log_printf(struct sieve_trace_log *trace_log,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+
+void sieve_trace_log_free(struct sieve_trace_log **_trace_log);
+
+int sieve_trace_config_get(struct sieve_instance *svinst,
+ struct sieve_trace_config *tr_config);
+
+/*
+ * Execution exit codes
+ */
+
+const char *sieve_execution_exitcode_to_str(int code);
+
+/*
+ * Resource usage
+ */
+
+/* Initialize the resource usage struct, clearing all usage statistics. */
+void sieve_resource_usage_init(struct sieve_resource_usage *rusage_r);
+
+/* Calculate the sum of the provided resource usage statistics, writing the
+ result to the first. */
+void sieve_resource_usage_add(struct sieve_resource_usage *dst,
+ const struct sieve_resource_usage *src);
+
+/* Returns TRUE if the resource usage is sufficiently high to warrant recording
+ for checking cumulative resource limits (across several different script
+ executions). */
+bool sieve_resource_usage_is_high(struct sieve_instance *svinst,
+ const struct sieve_resource_usage *rusage);
+/* Returns TRUE when the provided resource usage statistics exceed a configured
+ policy limit. */
+bool sieve_resource_usage_is_excessive(
+ struct sieve_instance *svinst,
+ const struct sieve_resource_usage *rusage);
+/* Returns a string containing a description of the resource usage (to be used
+ log messages). */
+const char *
+sieve_resource_usage_get_summary(const struct sieve_resource_usage *rusage);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/Makefile.am b/pigeonhole/src/lib-sieve/storage/Makefile.am
new file mode 100644
index 0000000..ba39e4a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = \
+ data \
+ file \
+ dict \
+ ldap
diff --git a/pigeonhole/src/lib-sieve/storage/Makefile.in b/pigeonhole/src/lib-sieve/storage/Makefile.in
new file mode 100644
index 0000000..d81c0c5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/Makefile.in
@@ -0,0 +1,697 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/storage
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = \
+ data \
+ file \
+ dict \
+ ldap
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/storage/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/storage/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/storage/data/Makefile.am b/pigeonhole/src/lib-sieve/storage/data/Makefile.am
new file mode 100644
index 0000000..c17741d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/data/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_storage_data.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_data_la_SOURCES = \
+ sieve-data-script.c \
+ sieve-data-storage.c
+
+noinst_HEADERS = \
+ sieve-data-storage.h
diff --git a/pigeonhole/src/lib-sieve/storage/data/Makefile.in b/pigeonhole/src/lib-sieve/storage/data/Makefile.in
new file mode 100644
index 0000000..199f2db
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/data/Makefile.in
@@ -0,0 +1,686 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/storage/data
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_storage_data_la_LIBADD =
+am_libsieve_storage_data_la_OBJECTS = sieve-data-script.lo \
+ sieve-data-storage.lo
+libsieve_storage_data_la_OBJECTS = \
+ $(am_libsieve_storage_data_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sieve-data-script.Plo \
+ ./$(DEPDIR)/sieve-data-storage.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_storage_data_la_SOURCES)
+DIST_SOURCES = $(libsieve_storage_data_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_storage_data.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_data_la_SOURCES = \
+ sieve-data-script.c \
+ sieve-data-storage.c
+
+noinst_HEADERS = \
+ sieve-data-storage.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/storage/data/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/storage/data/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_storage_data.la: $(libsieve_storage_data_la_OBJECTS) $(libsieve_storage_data_la_DEPENDENCIES) $(EXTRA_libsieve_storage_data_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_storage_data_la_OBJECTS) $(libsieve_storage_data_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-data-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-data-storage.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sieve-data-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-data-storage.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sieve-data-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-data-storage.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/storage/data/sieve-data-script.c b/pigeonhole/src/lib-sieve/storage/data/sieve-data-script.c
new file mode 100644
index 0000000..2241bc5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/data/sieve-data-script.c
@@ -0,0 +1,95 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "istream.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-data-storage.h"
+
+/*
+ * Script data implementation
+ */
+
+static struct sieve_data_script *sieve_data_script_alloc(void)
+{
+ struct sieve_data_script *dscript;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_data_script", 1024);
+ dscript = p_new(pool, struct sieve_data_script, 1);
+ dscript->script = sieve_data_script;
+ dscript->script.pool = pool;
+
+ return dscript;
+}
+
+struct sieve_script *sieve_data_script_create_from_input
+(struct sieve_instance *svinst, const char *name, struct istream *input)
+{
+ struct sieve_storage *storage;
+ struct sieve_data_script *dscript = NULL;
+
+ storage = sieve_storage_alloc(svinst, NULL, &sieve_data_storage,
+ "", 0, FALSE);
+
+ dscript = sieve_data_script_alloc();
+ sieve_script_init(&dscript->script,
+ storage, &sieve_data_script, "data:", name);
+
+ dscript->data = input;
+ i_stream_ref(dscript->data);
+
+ sieve_storage_unref(&storage);
+
+ dscript->script.open = TRUE;
+
+ return &dscript->script;
+}
+
+static void sieve_data_script_destroy(struct sieve_script *script)
+{
+ struct sieve_data_script *dscript =
+ (struct sieve_data_script *)script;
+
+ i_stream_unref(&dscript->data);
+}
+
+static int sieve_data_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_data_script *dscript =
+ (struct sieve_data_script *)script;
+
+ i_stream_ref(dscript->data);
+ i_stream_seek(dscript->data, 0);
+
+ *stream_r = dscript->data;
+ *error_r = SIEVE_ERROR_NONE;
+ return 0;
+}
+
+static bool sieve_data_script_equals
+(const struct sieve_script *script ATTR_UNUSED,
+ const struct sieve_script *other ATTR_UNUSED)
+{
+ return ( script == other );
+}
+
+const struct sieve_script sieve_data_script = {
+ .driver_name = SIEVE_DATA_STORAGE_DRIVER_NAME,
+ .v = {
+ .destroy = sieve_data_script_destroy,
+
+ .get_stream = sieve_data_script_get_stream,
+
+ .equals = sieve_data_script_equals
+ }
+};
diff --git a/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.c b/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.c
new file mode 100644
index 0000000..21b4f35
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+#include "sieve-data-storage.h"
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_data_storage_alloc(void)
+{
+ struct sieve_data_storage *dstorage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_data_storage", 1024);
+ dstorage = p_new(pool, struct sieve_data_storage, 1);
+ dstorage->storage = sieve_data_storage;
+ dstorage->storage.pool = pool;
+
+ return &dstorage->storage;
+}
+
+static int sieve_data_storage_init
+(struct sieve_storage *storage ATTR_UNUSED,
+ const char *const *options ATTR_UNUSED,
+ enum sieve_error *error_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_data_storage = {
+ .driver_name = SIEVE_DATA_STORAGE_DRIVER_NAME,
+ .version = 0,
+ .v = {
+ .alloc = sieve_data_storage_alloc,
+ .init = sieve_data_storage_init,
+ }
+};
diff --git a/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.h b/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.h
new file mode 100644
index 0000000..a200e25
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/data/sieve-data-storage.h
@@ -0,0 +1,30 @@
+#ifndef SIEVE_DATA_STORAGE_H
+#define SIEVE_DATA_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+/*
+ * Storage class
+ */
+
+struct sieve_data_storage {
+ struct sieve_storage storage;
+};
+
+/*
+ * Script class
+ */
+
+struct sieve_data_script {
+ struct sieve_script script;
+
+ struct istream *data;
+};
+
+struct sieve_script *sieve_data_script_create_from_input
+ (struct sieve_instance *svinst, const char *name,
+ struct istream *input);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/dict/Makefile.am b/pigeonhole/src/lib-sieve/storage/dict/Makefile.am
new file mode 100644
index 0000000..2a73f43
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/dict/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libsieve_storage_dict.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_dict_la_SOURCES = \
+ sieve-dict-script.c \
+ sieve-dict-storage.c
+
+noinst_HEADERS = \
+ sieve-dict-storage.h
diff --git a/pigeonhole/src/lib-sieve/storage/dict/Makefile.in b/pigeonhole/src/lib-sieve/storage/dict/Makefile.in
new file mode 100644
index 0000000..58fd0c5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/dict/Makefile.in
@@ -0,0 +1,686 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/storage/dict
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_storage_dict_la_LIBADD =
+am_libsieve_storage_dict_la_OBJECTS = sieve-dict-script.lo \
+ sieve-dict-storage.lo
+libsieve_storage_dict_la_OBJECTS = \
+ $(am_libsieve_storage_dict_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sieve-dict-script.Plo \
+ ./$(DEPDIR)/sieve-dict-storage.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_storage_dict_la_SOURCES)
+DIST_SOURCES = $(libsieve_storage_dict_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_storage_dict.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+libsieve_storage_dict_la_SOURCES = \
+ sieve-dict-script.c \
+ sieve-dict-storage.c
+
+noinst_HEADERS = \
+ sieve-dict-storage.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/storage/dict/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/storage/dict/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_storage_dict.la: $(libsieve_storage_dict_la_OBJECTS) $(libsieve_storage_dict_la_DEPENDENCIES) $(EXTRA_libsieve_storage_dict_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_storage_dict_la_OBJECTS) $(libsieve_storage_dict_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-dict-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-dict-storage.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sieve-dict-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-dict-storage.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sieve-dict-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-dict-storage.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c
new file mode 100644
index 0000000..eb28f7d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c
@@ -0,0 +1,343 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "strfuncs.h"
+#include "istream.h"
+#include "dict.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+#include "sieve-dict-storage.h"
+
+/*
+ * Script dict implementation
+ */
+
+static struct sieve_dict_script *sieve_dict_script_alloc(void)
+{
+ struct sieve_dict_script *dscript;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_dict_script", 1024);
+ dscript = p_new(pool, struct sieve_dict_script, 1);
+ dscript->script = sieve_dict_script;
+ dscript->script.pool = pool;
+
+ return dscript;
+}
+
+struct sieve_dict_script *sieve_dict_script_init
+(struct sieve_dict_storage *dstorage, const char *name)
+{
+ struct sieve_storage *storage = &dstorage->storage;
+ struct sieve_dict_script *dscript = NULL;
+ const char *location;
+
+ if ( name == NULL ) {
+ name = SIEVE_DICT_SCRIPT_DEFAULT;
+ location = storage->location;
+ } else {
+ location = t_strconcat
+ (storage->location, ";name=", name, NULL);
+ }
+
+ dscript = sieve_dict_script_alloc();
+ sieve_script_init(&dscript->script,
+ storage, &sieve_dict_script, location, name);
+
+ return dscript;
+}
+
+static void sieve_dict_script_destroy(struct sieve_script *script)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+
+ if ( dscript->data_pool != NULL )
+ pool_unref(&dscript->data_pool);
+}
+
+static int sieve_dict_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)storage;
+ const char *name = script->name;
+ const char *path, *data_id, *error;
+ int ret;
+
+ if ( sieve_dict_storage_get_dict
+ (dstorage, &dscript->dict, error_r) < 0 )
+ return -1;
+
+ path = t_strconcat
+ (DICT_SIEVE_NAME_PATH, dict_escape_string(name), NULL);
+
+ struct dict_op_settings set = {
+ .username = dstorage->username,
+ };
+ ret = dict_lookup
+ (dscript->dict, &set, script->pool, path, &data_id, &error);
+ if ( ret <= 0 ) {
+ if ( ret < 0 ) {
+ sieve_script_set_critical(script,
+ "Failed to lookup script id from path %s: %s", path, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ } else {
+ e_debug(script->event,
+ "Script `%s' not found at path %s", name, path);
+ sieve_script_set_error(script,
+ SIEVE_ERROR_NOT_FOUND,
+ "Sieve script `%s' not found", name);
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ }
+ return -1;
+ }
+
+ dscript->data_id = p_strdup(script->pool, data_id);
+ return 0;
+}
+
+static int sieve_dict_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)script->storage;
+ const char *path, *name = script->name, *data, *error;
+ int ret;
+
+ dscript->data_pool =
+ pool_alloconly_create("sieve_dict_script data pool", 1024);
+
+ path = t_strconcat
+ (DICT_SIEVE_DATA_PATH, dict_escape_string(dscript->data_id), NULL);
+
+ struct dict_op_settings set = {
+ .username = dstorage->username,
+ };
+ ret = dict_lookup
+ (dscript->dict, &set, dscript->data_pool, path, &data, &error);
+ if ( ret <= 0 ) {
+ if ( ret < 0 ) {
+ sieve_script_set_critical(script,
+ "Failed to lookup data with id `%s' "
+ "for script `%s' from path %s: %s",
+ dscript->data_id, name, path, error);
+ } else {
+ sieve_script_set_critical(script,
+ "Data with id `%s' for script `%s' "
+ "not found at path %s",
+ dscript->data_id, name, path);
+ }
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ dscript->data = p_strdup(script->pool, data);
+ *stream_r = i_stream_create_from_data(dscript->data, strlen(dscript->data));
+ return 0;
+}
+
+static int sieve_dict_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+ sieve_size_t *offset)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+ struct sieve_binary *sbin =
+ sieve_binary_block_get_binary(sblock);
+ string_t *data_id;
+
+ if ( dscript->data_id == NULL &&
+ sieve_script_open(script, NULL) < 0 )
+ return 0;
+
+ if ( !sieve_binary_read_string(sblock, offset, &data_id) ) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s'",
+ sieve_binary_path(sbin), sieve_script_location(script));
+ return -1;
+ }
+ i_assert( dscript->data_id != NULL );
+ if ( strcmp(str_c(data_id), dscript->data_id) != 0 ) {
+ e_debug(script->event,
+ "Binary `%s' reports different data ID for script `%s' "
+ "(`%s' rather than `%s')",
+ sieve_binary_path(sbin), sieve_script_location(script),
+ str_c(data_id), dscript->data_id);
+ return 0;
+ }
+ return 1;
+}
+
+static void sieve_dict_script_binary_write_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+
+ sieve_binary_emit_cstring(sblock, dscript->data_id);
+}
+
+static bool sieve_dict_script_binary_dump_metadata
+(struct sieve_script *script ATTR_UNUSED, struct sieve_dumptime_env *denv,
+ struct sieve_binary_block *sblock, sieve_size_t *offset)
+{
+ string_t *data_id;
+
+ if ( !sieve_binary_read_string(sblock, offset, &data_id) )
+ return FALSE;
+ sieve_binary_dumpf(denv, "dict.data_id = %s\n", str_c(data_id));
+
+ return TRUE;
+}
+
+static const char * sieve_dict_script_get_binpath
+(struct sieve_dict_script *dscript)
+{
+ struct sieve_script *script = &dscript->script;
+ struct sieve_storage *storage = script->storage;
+
+ if ( dscript->binpath == NULL ) {
+ if ( storage->bin_dir == NULL )
+ return NULL;
+ dscript->binpath = p_strconcat(script->pool,
+ storage->bin_dir, "/",
+ sieve_binfile_from_name(script->name), NULL);
+ }
+
+ return dscript->binpath;
+}
+
+static struct sieve_binary *sieve_dict_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+
+ if ( sieve_dict_script_get_binpath(dscript) == NULL )
+ return NULL;
+
+ return sieve_binary_open(script->storage->svinst,
+ dscript->binpath, script, error_r);
+}
+
+static int sieve_dict_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r)
+{
+ struct sieve_dict_script *dscript =
+ (struct sieve_dict_script *)script;
+
+ if ( sieve_dict_script_get_binpath(dscript) == NULL )
+ return 0;
+ if ( sieve_storage_setup_bindir(script->storage, 0700) < 0 )
+ return -1;
+
+ return sieve_binary_save(sbin,
+ dscript->binpath, update, 0600, error_r);
+}
+
+static bool sieve_dict_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+ struct sieve_storage *storage = script->storage;
+ struct sieve_storage *sother = other->storage;
+
+ if ( strcmp(storage->location, sother->location) != 0 )
+ return FALSE;
+
+ i_assert( script->name != NULL && other->name != NULL );
+
+ return ( strcmp(script->name, other->name) == 0 );
+}
+
+const struct sieve_script sieve_dict_script = {
+ .driver_name = SIEVE_DICT_STORAGE_DRIVER_NAME,
+ .v = {
+ .destroy = sieve_dict_script_destroy,
+
+ .open = sieve_dict_script_open,
+
+ .get_stream = sieve_dict_script_get_stream,
+
+ .binary_read_metadata =sieve_dict_script_binary_read_metadata,
+ .binary_write_metadata = sieve_dict_script_binary_write_metadata,
+ .binary_dump_metadata = sieve_dict_script_binary_dump_metadata,
+ .binary_load = sieve_dict_script_binary_load,
+ .binary_save = sieve_dict_script_binary_save,
+
+ .equals = sieve_dict_script_equals
+ }
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_dict_script_sequence {
+ struct sieve_script_sequence seq;
+
+ bool done:1;
+};
+
+struct sieve_script_sequence *sieve_dict_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+ struct sieve_dict_script_sequence *dseq = NULL;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+
+ /* Create sequence object */
+ dseq = i_new(struct sieve_dict_script_sequence, 1);
+ sieve_script_sequence_init(&dseq->seq, storage);
+
+ return &dseq->seq;
+}
+
+struct sieve_script *sieve_dict_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+ struct sieve_dict_script_sequence *dseq =
+ (struct sieve_dict_script_sequence *)seq;
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)seq->storage;
+ struct sieve_dict_script *dscript;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+
+ if ( dseq->done )
+ return NULL;
+ dseq->done = TRUE;
+
+ dscript = sieve_dict_script_init
+ (dstorage, seq->storage->script_name);
+ if ( sieve_script_open(&dscript->script, error_r) < 0 ) {
+ struct sieve_script *script = &dscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return &dscript->script;
+}
+
+void sieve_dict_script_sequence_destroy(struct sieve_script_sequence *seq)
+{
+ struct sieve_dict_script_sequence *dseq =
+ (struct sieve_dict_script_sequence *)seq;
+ i_free(dseq);
+}
+
diff --git a/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c
new file mode 100644
index 0000000..7f50816
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c
@@ -0,0 +1,193 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "dict.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+
+#include "sieve-dict-storage.h"
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_dict_storage_alloc(void)
+{
+ struct sieve_dict_storage *dstorage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_dict_storage", 1024);
+ dstorage = p_new(pool, struct sieve_dict_storage, 1);
+ dstorage->storage = sieve_dict_storage;
+ dstorage->storage.pool = pool;
+
+ return &dstorage->storage;
+}
+
+static int sieve_dict_storage_init
+(struct sieve_storage *storage, const char *const *options,
+ enum sieve_error *error_r)
+{
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *uri = storage->location, *username = NULL;
+
+ if ( options != NULL ) {
+ while ( *options != NULL ) {
+ const char *option = *options;
+
+ if ( strncasecmp(option, "user=", 5) == 0 && option[5] != '\0' ) {
+ username = option+5;
+ } else {
+ sieve_storage_set_critical(storage,
+ "Invalid option `%s'", option);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ options++;
+ }
+ }
+
+ if ( username == NULL ) {
+ if ( svinst->username == NULL ) {
+ sieve_storage_set_critical(storage,
+ "No username specified");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ username = svinst->username;
+ }
+
+ if ( svinst->base_dir == NULL ) {
+ sieve_storage_set_critical(storage,
+ "BUG: Sieve interpreter is initialized without a base_dir");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ e_debug(storage->event, "user=%s, uri=%s", username, uri);
+
+ dstorage->uri = p_strdup(storage->pool, uri);
+ dstorage->username = p_strdup(storage->pool, username);
+
+ storage->location = p_strconcat(storage->pool,
+ SIEVE_DICT_STORAGE_DRIVER_NAME, ":", storage->location,
+ ";user=", username, NULL);
+
+ return 0;
+}
+
+int sieve_dict_storage_get_dict
+(struct sieve_dict_storage *dstorage, struct dict **dict_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &dstorage->storage;
+ struct sieve_instance *svinst = storage->svinst;
+ struct dict_settings dict_set;
+ const char *error;
+ int ret;
+
+ if ( dstorage->dict == NULL ) {
+ i_zero(&dict_set);
+ dict_set.base_dir = svinst->base_dir;
+ ret = dict_init(dstorage->uri, &dict_set, &dstorage->dict, &error);
+ if ( ret < 0 ) {
+ sieve_storage_set_critical(storage,
+ "Failed to initialize dict with data `%s' for user `%s': %s",
+ dstorage->uri, dstorage->username, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ }
+
+ *dict_r = dstorage->dict;
+ return 0;
+}
+
+static void sieve_dict_storage_destroy(struct sieve_storage *storage)
+{
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)storage;
+
+ if ( dstorage->dict != NULL )
+ dict_deinit(&dstorage->dict);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_dict_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)storage;
+ struct sieve_dict_script *dscript;
+
+ T_BEGIN {
+ dscript = sieve_dict_script_init(dstorage, name);
+ } T_END;
+
+ return &dscript->script;
+}
+
+/*
+ * Active script
+ */
+
+struct sieve_script *sieve_dict_storage_active_script_open
+(struct sieve_storage *storage)
+{
+ struct sieve_dict_storage *dstorage =
+ (struct sieve_dict_storage *)storage;
+ struct sieve_dict_script *dscript;
+
+ dscript = sieve_dict_script_init
+ (dstorage, storage->script_name);
+ if ( sieve_script_open(&dscript->script, NULL) < 0 ) {
+ struct sieve_script *script = &dscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return &dscript->script;
+}
+
+int sieve_dict_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+ if ( storage->script_name != NULL )
+ *name_r = storage->script_name;
+ else
+ *name_r = SIEVE_DICT_SCRIPT_DEFAULT;
+ return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_dict_storage = {
+ .driver_name = SIEVE_DICT_STORAGE_DRIVER_NAME,
+ .version = 0,
+ .v = {
+ .alloc = sieve_dict_storage_alloc,
+ .destroy = sieve_dict_storage_destroy,
+ .init = sieve_dict_storage_init,
+
+ .get_script = sieve_dict_storage_get_script,
+
+ .get_script_sequence = sieve_dict_storage_get_script_sequence,
+ .script_sequence_next = sieve_dict_script_sequence_next,
+ .script_sequence_destroy = sieve_dict_script_sequence_destroy,
+
+ .active_script_get_name = sieve_dict_storage_active_script_get_name,
+ .active_script_open = sieve_dict_storage_active_script_open,
+
+ // FIXME: impement management interface
+ }
+};
diff --git a/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.h b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.h
new file mode 100644
index 0000000..1f92221
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.h
@@ -0,0 +1,66 @@
+#ifndef SIEVE_DICT_STORAGE_H
+#define SIEVE_DICT_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#define DICT_SIEVE_PATH DICT_PATH_PRIVATE"sieve/"
+#define DICT_SIEVE_NAME_PATH DICT_SIEVE_PATH"name/"
+#define DICT_SIEVE_DATA_PATH DICT_SIEVE_PATH"data/"
+
+#define SIEVE_DICT_SCRIPT_DEFAULT "default"
+
+/*
+ * Storage class
+ */
+
+struct sieve_dict_storage {
+ struct sieve_storage storage;
+
+ const char *username;
+ const char *uri;
+
+ struct dict *dict;
+};
+
+int sieve_dict_storage_get_dict
+ (struct sieve_dict_storage *dstorage, struct dict **dict_r,
+ enum sieve_error *error_r);
+
+struct sieve_script *sieve_dict_storage_active_script_open
+ (struct sieve_storage *storage);
+int sieve_dict_storage_active_script_get_name
+ (struct sieve_storage *storage, const char **name_r);
+
+/*
+ * Script class
+ */
+
+struct sieve_dict_script {
+ struct sieve_script script;
+
+ struct dict *dict;
+
+ pool_t data_pool;
+ const char *data_id;
+ const char *data;
+
+ const char *binpath;
+};
+
+struct sieve_dict_script *sieve_dict_script_init
+ (struct sieve_dict_storage *dstorage, const char *name);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_dict_storage_get_script_sequence
+ (struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_dict_script_sequence_next
+ (struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_dict_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/file/Makefile.am b/pigeonhole/src/lib-sieve/storage/file/Makefile.am
new file mode 100644
index 0000000..b401fa8
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libsieve_storage_file.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve \
+ -I$(top_srcdir)/src/lib-sieve/util
+
+libsieve_storage_file_la_SOURCES = \
+ sieve-file-script.c \
+ sieve-file-script-sequence.c \
+ sieve-file-storage-active.c \
+ sieve-file-storage-save.c \
+ sieve-file-storage-list.c \
+ sieve-file-storage-quota.c \
+ sieve-file-storage.c
+
+noinst_HEADERS = \
+ sieve-file-storage.h
diff --git a/pigeonhole/src/lib-sieve/storage/file/Makefile.in b/pigeonhole/src/lib-sieve/storage/file/Makefile.in
new file mode 100644
index 0000000..8432831
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/Makefile.in
@@ -0,0 +1,714 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-sieve/storage/file
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_storage_file_la_LIBADD =
+am_libsieve_storage_file_la_OBJECTS = sieve-file-script.lo \
+ sieve-file-script-sequence.lo sieve-file-storage-active.lo \
+ sieve-file-storage-save.lo sieve-file-storage-list.lo \
+ sieve-file-storage-quota.lo sieve-file-storage.lo
+libsieve_storage_file_la_OBJECTS = \
+ $(am_libsieve_storage_file_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/sieve-file-script-sequence.Plo \
+ ./$(DEPDIR)/sieve-file-script.Plo \
+ ./$(DEPDIR)/sieve-file-storage-active.Plo \
+ ./$(DEPDIR)/sieve-file-storage-list.Plo \
+ ./$(DEPDIR)/sieve-file-storage-quota.Plo \
+ ./$(DEPDIR)/sieve-file-storage-save.Plo \
+ ./$(DEPDIR)/sieve-file-storage.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_storage_file_la_SOURCES)
+DIST_SOURCES = $(libsieve_storage_file_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_storage_file.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve \
+ -I$(top_srcdir)/src/lib-sieve/util
+
+libsieve_storage_file_la_SOURCES = \
+ sieve-file-script.c \
+ sieve-file-script-sequence.c \
+ sieve-file-storage-active.c \
+ sieve-file-storage-save.c \
+ sieve-file-storage-list.c \
+ sieve-file-storage-quota.c \
+ sieve-file-storage.c
+
+noinst_HEADERS = \
+ sieve-file-storage.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/storage/file/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/storage/file/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_storage_file.la: $(libsieve_storage_file_la_OBJECTS) $(libsieve_storage_file_la_DEPENDENCIES) $(EXTRA_libsieve_storage_file_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_storage_file_la_OBJECTS) $(libsieve_storage_file_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-script-sequence.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-storage-active.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-storage-list.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-storage-quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-storage-save.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-file-storage.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sieve-file-script-sequence.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-active.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-list.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-quota.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-save.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/sieve-file-script-sequence.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-active.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-list.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-quota.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage-save.Plo
+ -rm -f ./$(DEPDIR)/sieve-file-storage.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-script-sequence.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script-sequence.c
new file mode 100644
index 0000000..a1ae75d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script-sequence.c
@@ -0,0 +1,244 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+
+/*
+ * Script sequence
+ */
+
+struct sieve_file_script_sequence {
+ struct sieve_script_sequence seq;
+ pool_t pool;
+
+ ARRAY_TYPE(const_string) script_files;
+ unsigned int index;
+
+ bool storage_is_file:1;
+};
+
+static int sieve_file_script_sequence_read_dir
+(struct sieve_file_script_sequence *fseq, const char *path)
+{
+ struct sieve_storage *storage = fseq->seq.storage;
+ DIR *dirp;
+ int ret = 0;
+
+ /* Open the directory */
+ if ( (dirp = opendir(path)) == NULL ) {
+ switch ( errno ) {
+ case ENOENT:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NOT_FOUND,
+ "Script sequence location not found");
+ break;
+ case EACCES:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_PERMISSION,
+ "Script sequence location not accessible");
+ e_error(storage->event,
+ "Failed to open sieve sequence: %s",
+ eacces_error_get("stat", path));
+ break;
+ default:
+ sieve_storage_set_critical(storage,
+ "Failed to open sieve sequence: "
+ "opendir(%s) failed: %m", path);
+ break;
+ }
+ return -1;
+ }
+
+ /* Read and sort script files */
+ for (;;) {
+ const char *const *files;
+ unsigned int count, i;
+ const char *file;
+ struct dirent *dp;
+ struct stat st;
+
+ errno = 0;
+ if ( (dp=readdir(dirp)) == NULL )
+ break;
+
+ if ( !sieve_script_file_has_extension(dp->d_name) )
+ continue;
+
+ file = NULL;
+ T_BEGIN {
+ if ( path[strlen(path)-1] == '/' )
+ file = t_strconcat(path, dp->d_name, NULL);
+ else
+ file = t_strconcat(path, "/", dp->d_name, NULL);
+
+ if ( stat(file, &st) == 0 && S_ISREG(st.st_mode) )
+ file = p_strdup(fseq->pool, dp->d_name);
+ else
+ file = NULL;
+ } T_END;
+
+ if (file == NULL)
+ continue;
+
+ /* Insert into sorted array */
+ files = array_get(&fseq->script_files, &count);
+ for ( i = 0; i < count; i++ ) {
+ if ( strcmp(file, files[i]) < 0 )
+ break;
+ }
+
+ if ( i == count )
+ array_append(&fseq->script_files, &file, 1);
+ else
+ array_insert(&fseq->script_files, i, &file, 1);
+ }
+
+ if ( errno != 0 ) {
+ sieve_storage_set_critical(storage,
+ "Failed to read sequence directory: "
+ "readdir(%s) failed: %m", path);
+ ret = -1;
+ }
+
+ /* Close the directory */
+ if ( dirp != NULL && closedir(dirp) < 0 ) {
+ e_error(storage->event,
+ "Failed to close sequence directory: "
+ "closedir(%s) failed: %m", path);
+ }
+ return ret;
+}
+
+struct sieve_script_sequence *sieve_file_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct sieve_file_script_sequence *fseq = NULL;
+ const char *name = storage->script_name;
+ const char *file;
+ pool_t pool;
+ struct stat st;
+
+ /* Specified path can either be a regular file or a directory */
+ if ( stat(fstorage->path, &st) != 0 ) {
+ switch ( errno ) {
+ case ENOENT:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NOT_FOUND,
+ "Script sequence location not found");
+ break;
+ case EACCES:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_PERMISSION,
+ "Script sequence location not accessible");
+ e_error(storage->event,
+ "Failed to open sieve sequence: %s",
+ eacces_error_get("stat", fstorage->path));
+ break;
+ default:
+ sieve_storage_set_critical(storage,
+ "Failed to open sieve sequence: "
+ "stat(%s) failed: %m", fstorage->path);
+ break;
+ }
+ *error_r = storage->error_code;
+ return NULL;
+ }
+
+ /* Create sequence object */
+ pool = pool_alloconly_create("sieve_file_script_sequence", 1024);
+ fseq = p_new(pool, struct sieve_file_script_sequence, 1);
+ fseq->pool = pool;
+ sieve_script_sequence_init(&fseq->seq, storage);
+
+ if ( S_ISDIR(st.st_mode) ) {
+ i_array_init(&fseq->script_files, 16);
+
+ /* Path is directory */
+ if (name == 0 || *name == '\0') {
+ /* Read all '.sieve' files in directory */
+ if (sieve_file_script_sequence_read_dir
+ (fseq, fstorage->path) < 0) {
+ *error_r = storage->error_code;
+ sieve_file_script_sequence_destroy(&fseq->seq);
+ return NULL;
+ }
+
+ } else {
+ /* Read specific script file */
+ file = sieve_script_file_from_name(name);
+ file = p_strdup(pool, file);
+ array_append(&fseq->script_files, &file, 1);
+ }
+
+ } else {
+ /* Path is a file
+ (apparently; we'll see about that once it is opened) */
+ fseq->storage_is_file = TRUE;
+ }
+
+ return &fseq->seq;
+}
+
+struct sieve_script *sieve_file_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+ struct sieve_file_script_sequence *fseq =
+ (struct sieve_file_script_sequence *)seq;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)seq->storage;
+ struct sieve_file_script *fscript;
+ const char *const *files;
+ unsigned int count;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+
+ fscript = NULL;
+ if ( fseq->storage_is_file ) {
+ if ( fseq->index++ < 1 )
+ fscript = sieve_file_script_open_from_name(fstorage, NULL);
+
+ } else {
+ files = array_get(&fseq->script_files, &count);
+
+ while ( fseq->index < count ) {
+ fscript = sieve_file_script_open_from_filename
+ (fstorage, files[fseq->index++], NULL);
+ if (fscript != NULL)
+ break;
+ if (seq->storage->error_code != SIEVE_ERROR_NOT_FOUND)
+ break;
+ sieve_storage_clear_error(seq->storage);
+ }
+ }
+
+ if (fscript == NULL ) {
+ if ( error_r != NULL )
+ *error_r = seq->storage->error_code;
+ return NULL;
+ }
+ return &fscript->script;
+}
+
+void sieve_file_script_sequence_destroy(struct sieve_script_sequence *seq)
+{
+ struct sieve_file_script_sequence *fseq =
+ (struct sieve_file_script_sequence *)seq;
+
+ if ( array_is_created(&fseq->script_files) )
+ array_free(&fseq->script_files);
+ pool_unref(&fseq->pool);
+}
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
new file mode 100644
index 0000000..3c4ec60
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
@@ -0,0 +1,832 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "path-util.h"
+#include "istream.h"
+#include "time-util.h"
+#include "eacces-error.h"
+
+#include "sieve-binary.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <fcntl.h>
+
+/*
+ * Filename to name/name to filename
+ */
+
+const char *sieve_script_file_get_scriptname(const char *filename)
+{
+ const char *ext;
+
+ /* Extract the script name */
+ ext = strrchr(filename, '.');
+ if ( ext == NULL || ext == filename ||
+ strcmp(ext, "."SIEVE_SCRIPT_FILEEXT) != 0 )
+ return NULL;
+
+ return t_strdup_until(filename, ext);
+}
+
+bool sieve_script_file_has_extension(const char *filename)
+{
+ return ( sieve_script_file_get_scriptname(filename) != NULL );
+}
+
+const char *sieve_script_file_from_name(const char *name)
+{
+ return t_strconcat(name, "."SIEVE_SCRIPT_FILEEXT, NULL);
+}
+
+/*
+ * Common error handling
+ */
+
+static void sieve_file_script_handle_error
+(struct sieve_file_script *fscript, const char *op, const char *path,
+ const char *name, enum sieve_error *error_r)
+{
+ struct sieve_script *script = &fscript->script;
+ const char *abspath, *error;
+
+ switch ( errno ) {
+ case ENOENT:
+ if (t_abspath(path, &abspath, &error) < 0) {
+ sieve_script_set_error(script,
+ SIEVE_ERROR_TEMP_FAILURE,
+ "t_abspath(%s) failed: %s",
+ path, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ break;
+ }
+ e_debug(script->event, "File `%s' not found", abspath);
+ sieve_script_set_error(script,
+ SIEVE_ERROR_NOT_FOUND,
+ "Sieve script `%s' not found", name);
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ break;
+ case EACCES:
+ sieve_script_set_critical(script,
+ "Failed to %s sieve script: %s",
+ op, eacces_error_get(op, path));
+ *error_r = SIEVE_ERROR_NO_PERMISSION;
+ break;
+ default:
+ sieve_script_set_critical(script,
+ "Failed to %s sieve script: %s(%s) failed: %m",
+ op, op, path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ break;
+ }
+}
+
+/*
+ *
+ */
+
+static struct sieve_file_script *sieve_file_script_alloc(void)
+{
+ struct sieve_file_script *fscript;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_file_script", 2048);
+ fscript = p_new(pool, struct sieve_file_script, 1);
+ fscript->script = sieve_file_script;
+ fscript->script.pool = pool;
+
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_filename
+(struct sieve_file_storage *fstorage, const char *filename,
+ const char *scriptname)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_file_script *fscript = NULL;
+
+ /* Prevent initializing the active script link as a script when it
+ * resides in the sieve storage directory.
+ */
+ if ( scriptname != NULL && fstorage->link_path != NULL &&
+ *(fstorage->link_path) == '\0' ) {
+ if ( strcmp(filename, fstorage->active_fname) == 0 ) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NOT_FOUND,
+ "Script `%s' does not exist.", scriptname);
+ return NULL;
+ }
+ }
+
+ fscript = sieve_file_script_alloc();
+ sieve_script_init
+ (&fscript->script, storage, &sieve_file_script,
+ sieve_file_storage_path_extend(fstorage, filename), scriptname);
+ fscript->filename = p_strdup(fscript->script.pool, filename);
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_filename
+(struct sieve_file_storage *fstorage, const char *filename,
+ const char *scriptname)
+{
+ struct sieve_file_script *fscript;
+ enum sieve_error error;
+
+ fscript = sieve_file_script_init_from_filename
+ (fstorage, filename, scriptname);
+ if ( fscript == NULL )
+ return NULL;
+
+ if ( sieve_script_open(&fscript->script, &error) < 0 ) {
+ struct sieve_script *script = &fscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_name
+(struct sieve_file_storage *fstorage, const char *name)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_file_script *fscript;
+
+ if (name != NULL && S_ISDIR(fstorage->st.st_mode)) {
+ return sieve_file_script_init_from_filename
+ (fstorage, sieve_script_file_from_name(name), name);
+ }
+
+ fscript = sieve_file_script_alloc();
+ sieve_script_init
+ (&fscript->script, storage, &sieve_file_script,
+ fstorage->active_path, name);
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_name
+(struct sieve_file_storage *fstorage, const char *name)
+{
+ struct sieve_file_script *fscript;
+ enum sieve_error error;
+
+ fscript = sieve_file_script_init_from_name(fstorage, name);
+ if ( fscript == NULL )
+ return NULL;
+
+ if ( sieve_script_open(&fscript->script, &error) < 0 ) {
+ struct sieve_script *script = &fscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_init_from_path
+(struct sieve_file_storage *fstorage, const char *path,
+ const char *scriptname, enum sieve_error *error_r)
+{
+ struct sieve_instance *svinst = fstorage->storage.svinst;
+ struct sieve_file_storage *fsubstorage;
+ struct sieve_file_script *fscript;
+ struct sieve_storage *substorage;
+ enum sieve_error error;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ fsubstorage = sieve_file_storage_init_from_path
+ (svinst, path, 0, error_r);
+ if (fsubstorage == NULL)
+ return NULL;
+ substorage = &fsubstorage->storage;
+
+ fscript = sieve_file_script_alloc();
+ sieve_script_init(&fscript->script,
+ substorage, &sieve_file_script, path, scriptname);
+ sieve_storage_unref(&substorage);
+
+ return fscript;
+}
+
+struct sieve_file_script *sieve_file_script_open_from_path
+(struct sieve_file_storage *fstorage, const char *path,
+ const char *scriptname, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_file_script *fscript;
+ enum sieve_error error;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+ else
+ error_r = &error;
+
+ fscript = sieve_file_script_init_from_path
+ (fstorage, path, scriptname, error_r);
+ if (fscript == NULL) {
+ sieve_storage_set_error(storage,
+ *error_r, "Failed to open script");
+ return NULL;
+ }
+
+ if ( sieve_script_open(&fscript->script, error_r) < 0 ) {
+ struct sieve_script *script = &fscript->script;
+ const char *errormsg;
+
+ errormsg = sieve_script_get_last_error(&fscript->script, error_r);
+ sieve_storage_set_error(storage,
+ *error_r, "%s", errormsg);
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return fscript;
+}
+
+/*
+ * Open
+ */
+
+static int sieve_file_script_stat
+(const char *path, struct stat *st, struct stat *lnk_st)
+{
+ if ( lstat(path, st) < 0 )
+ return -1;
+
+ *lnk_st = *st;
+
+ if ( S_ISLNK(st->st_mode) && stat(path, st) < 0 )
+ return -1;
+
+ return 0;
+}
+
+static const char *
+path_split_filename(const char *path, const char **dirpath_r)
+{
+ const char *filename;
+
+ filename = strrchr(path, '/');
+ if ( filename == NULL ) {
+ *dirpath_r = "";
+ filename = path;
+ } else {
+ *dirpath_r = t_strdup_until(path, filename);
+ filename++;
+ }
+ return filename;
+}
+
+static int sieve_file_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *)script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ pool_t pool = script->pool;
+ const char *filename, *name, *path;
+ const char *dirpath, *basename, *binpath, *binprefix;
+ struct stat st, lnk_st;
+ bool success = TRUE;
+ int ret = 0;
+
+ filename = fscript->filename;
+ basename = NULL;
+ name = script->name;
+ st = fstorage->st;
+ lnk_st = fstorage->lnk_st;
+
+ if (name == NULL)
+ name = storage->script_name;
+
+ T_BEGIN {
+ if ( S_ISDIR(st.st_mode) ) {
+ /* Storage is a directory */
+ path = fstorage->path;
+
+ if ( (filename == NULL || *filename == '\0') &&
+ name != NULL && *name != '\0' ) {
+ /* Name is used to find actual filename */
+ filename = sieve_script_file_from_name(name);
+ basename = name;
+ }
+ if ( filename == NULL || *filename == '\0' ) {
+ sieve_script_set_critical(script,
+ "Sieve script file path '%s' is a directory.", path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ success = FALSE;
+ } else {
+ /* Extend storage path with filename */
+ if (name == NULL) {
+ if ( basename == NULL &&
+ (basename=sieve_script_file_get_scriptname(filename)) == NULL )
+ basename = filename;
+ name = basename;
+ } else if (basename == NULL) {
+ basename = name;
+ }
+ dirpath = path;
+
+ path = sieve_file_storage_path_extend(fstorage, filename);
+ ret = sieve_file_script_stat(path, &st, &lnk_st);
+ }
+
+ } else {
+ /* Storage is a single file */
+ path = fstorage->active_path;
+
+ /* Extract filename from path */
+ filename = path_split_filename(path, &dirpath);
+
+ if ( (basename=sieve_script_file_get_scriptname(filename)) == NULL )
+ basename = filename;
+
+ if ( name == NULL )
+ name = basename;
+ }
+
+ if ( success ) {
+ if ( ret < 0 ) {
+ /* Make sure we have a script name for the error */
+ if ( name == NULL ) {
+ i_assert( basename != NULL );
+ name = basename;
+ }
+ sieve_file_script_handle_error
+ (fscript, "stat", path, name, error_r);
+ success = FALSE;
+
+ } else if ( !S_ISREG(st.st_mode) ) {
+ sieve_script_set_critical(script,
+ "Sieve script file '%s' is not a regular file.", path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ success = FALSE;
+ }
+ }
+
+ if ( success ) {
+ const char *bpath, *bfile, *bprefix;
+
+ if ( storage->bin_dir != NULL ) {
+ bpath = storage->bin_dir;
+ bfile = sieve_binfile_from_name(name);
+ bprefix = name;
+
+ } else {
+ bpath = dirpath;
+ bfile = sieve_binfile_from_name(basename);
+ bprefix = basename;
+ }
+
+ if ( *bpath == '\0' ) {
+ binpath = bfile;
+ binprefix = bprefix;
+ } else if ( bpath[strlen(bpath)-1] == '/' ) {
+ binpath = t_strconcat(bpath, bfile, NULL);
+ binprefix = t_strconcat(bpath, bprefix, NULL);
+ } else {
+ binpath = t_strconcat(bpath, "/", bfile, NULL);
+ binprefix = t_strconcat(bpath, "/", bprefix, NULL);
+ }
+
+ fscript->st = st;
+ fscript->lnk_st = lnk_st;
+ fscript->path = p_strdup(pool, path);
+ fscript->filename = p_strdup(pool, filename);
+ fscript->dirpath = p_strdup(pool, dirpath);
+ fscript->binpath = p_strdup(pool, binpath);
+ fscript->binprefix = p_strdup(pool, binprefix);
+
+ fscript->script.location = fscript->path;
+
+ if ( fscript->script.name == NULL )
+ fscript->script.name = p_strdup(pool, basename);
+ }
+ } T_END;
+
+ return ( success ? 0 : -1 );
+}
+
+static int sieve_file_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+ struct stat st;
+ struct istream *result;
+ int fd;
+
+ if ( (fd=open(fscript->path, O_RDONLY)) < 0 ) {
+ sieve_file_script_handle_error
+ (fscript, "open", fscript->path, fscript->script.name, error_r);
+ return -1;
+ }
+
+ if ( fstat(fd, &st) != 0 ) {
+ sieve_script_set_critical(script,
+ "Failed to open sieve script: fstat(fd=%s) failed: %m",
+ fscript->path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ result = NULL;
+ } else {
+ /* Re-check the file type just to be sure */
+ if ( !S_ISREG(st.st_mode) ) {
+ sieve_script_set_critical(script,
+ "Sieve script file `%s' is not a regular file", fscript->path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ result = NULL;
+ } else {
+ result = i_stream_create_fd_autoclose(&fd, SIEVE_FILE_READ_BLOCK_SIZE);
+ fscript->st = fscript->lnk_st = st;
+ }
+ }
+
+ if ( result == NULL ) {
+ /* Something went wrong, close the fd */
+ if ( fd >= 0 && close(fd) != 0 ) {
+ e_error(script->event,
+ "Failed to close sieve script: "
+ "close(fd=%s) failed: %m", fscript->path);
+ }
+ return -1;
+ }
+
+ *stream_r = result;
+ return 0;
+}
+
+/*
+ * Binary
+ */
+
+static int sieve_file_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+ sieve_size_t *offset ATTR_UNUSED)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+ struct sieve_instance *svinst = script->storage->svinst;
+ struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
+ const struct stat *sstat, *bstat;
+
+ bstat = sieve_binary_stat(sbin);
+ if ( fscript->st.st_mtime > fscript->lnk_st.st_mtime ||
+ (fscript->st.st_mtime == fscript->lnk_st.st_mtime &&
+ ST_MTIME_NSEC(fscript->st) >= ST_MTIME_NSEC(fscript->lnk_st)) ) {
+ sstat = &fscript->st;
+ } else {
+ sstat = &fscript->lnk_st;
+ }
+
+ if ( bstat->st_mtime < sstat->st_mtime ||
+ (bstat->st_mtime == sstat->st_mtime &&
+ ST_MTIME_NSEC(*bstat) <= ST_MTIME_NSEC(*sstat)) ) {
+ if ( svinst->debug ) {
+ e_debug(script->event,
+ "Sieve binary `%s' is not newer "
+ "than the Sieve script `%s' (%s.%lu <= %s.%lu)",
+ sieve_binary_path(sbin), sieve_script_location(script),
+ t_strflocaltime("%Y-%m-%d %H:%M:%S", bstat->st_mtime),
+ ST_MTIME_NSEC(*bstat),
+ t_strflocaltime("%Y-%m-%d %H:%M:%S", sstat->st_mtime),
+ ST_MTIME_NSEC(*sstat));
+ }
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct sieve_binary *sieve_file_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+ struct sieve_instance *svinst = script->storage->svinst;
+
+ return sieve_binary_open(svinst, fscript->binpath, script, error_r);
+}
+
+static int sieve_file_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = script->storage;
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+ if ( storage->bin_dir != NULL &&
+ sieve_storage_setup_bindir(storage, 0700) < 0 )
+ return -1;
+
+ return sieve_binary_save(sbin, fscript->binpath, update,
+ fscript->st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), error_r);
+}
+
+static const char *sieve_file_script_binary_get_prefix
+(struct sieve_script *script)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+ return fscript->binprefix;
+}
+
+/*
+ * Management
+ */
+
+static int sieve_file_storage_script_is_active(struct sieve_script *script)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *) script;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)script->storage;
+ const char *afile;
+ int ret = 0;
+
+ T_BEGIN {
+ ret = sieve_file_storage_active_script_get_file(fstorage, &afile);
+
+ if ( ret > 0 ) {
+ /* Is the requested script active? */
+ ret = ( strcmp(fscript->filename, afile) == 0 ? 1 : 0 );
+ }
+ } T_END;
+
+ return ret;
+}
+
+static int sieve_file_storage_script_delete(struct sieve_script *script)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *)script;
+ int ret = 0;
+
+ if ( sieve_file_storage_pre_modify(script->storage) < 0 )
+ return -1;
+
+ ret = unlink(fscript->path);
+ if ( ret < 0 ) {
+ if ( errno == ENOENT ) {
+ sieve_script_set_error(script,
+ SIEVE_ERROR_NOT_FOUND,
+ "Sieve script does not exist.");
+ } else {
+ sieve_script_set_critical(script,
+ "Performing unlink() failed on sieve file `%s': %m",
+ fscript->path);
+ }
+ }
+ return ret;
+}
+
+static int _sieve_file_storage_script_activate
+(struct sieve_file_script *fscript)
+{
+ struct sieve_script *script = &fscript->script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct stat st;
+ const char *link_path, *afile;
+ int activated = 0;
+ int ret;
+
+ /* Find out whether there is an active script, but recreate
+ * the symlink either way. This way, any possible error in the symlink
+ * resolves automatically. This step is only necessary to provide a
+ * proper return value indicating whether the script was already active.
+ */
+ ret = sieve_file_storage_active_script_get_file(fstorage, &afile);
+
+ /* Is the requested script already active? */
+ if ( ret <= 0 || strcmp(fscript->filename, afile) != 0 )
+ activated = 1;
+
+ i_assert( fstorage->link_path != NULL );
+
+ /* Check the scriptfile we are trying to activate */
+ if ( lstat(fscript->path, &st) != 0 ) {
+ sieve_script_set_critical(script,
+ "Failed to activate Sieve script: lstat(%s) failed: %m.",
+ fscript->path);
+ return -1;
+ }
+
+ /* Rescue a possible .dovecot.sieve regular file remaining from old
+ * installations.
+ */
+ if ( !sieve_file_storage_active_rescue_regular(fstorage) ) {
+ /* Rescue failed, manual intervention is necessary */
+ return -1;
+ }
+
+ /* Just try to create the symlink first */
+ link_path = t_strconcat
+ ( fstorage->link_path, fscript->filename, NULL );
+
+ ret = symlink(link_path, fstorage->active_path);
+ if ( ret < 0 ) {
+ if ( errno == EEXIST ) {
+ ret = sieve_file_storage_active_replace_link(fstorage, link_path);
+ if ( ret < 0 ) {
+ return ret;
+ }
+ } else {
+ /* Other error, critical */
+ sieve_script_set_critical(script,
+ "Failed to activate Sieve script: "
+ "symlink(%s, %s) failed: %m",
+ link_path, fstorage->active_path);
+ return -1;
+ }
+ }
+ return activated;
+}
+
+static int sieve_file_storage_script_activate
+(struct sieve_script *script)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *)script;
+ int ret;
+
+ if ( sieve_file_storage_pre_modify(script->storage) < 0 )
+ return -1;
+
+ T_BEGIN {
+ ret = _sieve_file_storage_script_activate(fscript);
+ } T_END;
+
+ return ret;
+}
+
+static int sieve_file_storage_script_rename
+(struct sieve_script *script, const char *newname)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *)script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ const char *newpath, *newfile, *link_path;
+ int ret = 0;
+
+ if ( sieve_file_storage_pre_modify(storage) < 0 )
+ return -1;
+
+ T_BEGIN {
+ newfile = sieve_script_file_from_name(newname);
+ newpath = t_strconcat( fstorage->path, "/", newfile, NULL );
+
+ /* The normal rename() system call overwrites the existing file without
+ * notice. Also, active scripts must not be disrupted by renaming a script.
+ * That is why we use a link(newpath) [activate newpath] unlink(oldpath)
+ */
+
+ /* Link to the new path */
+ ret = link(fscript->path, newpath);
+ if ( ret >= 0 ) {
+ /* Is the requested script active? */
+ if ( sieve_script_is_active(script) > 0 ) {
+ /* Active; make active link point to the new copy */
+ i_assert( fstorage->link_path != NULL );
+ link_path = t_strconcat
+ ( fstorage->link_path, newfile, NULL );
+
+ ret = sieve_file_storage_active_replace_link(fstorage, link_path);
+ }
+
+ if ( ret >= 0 ) {
+ /* If all is good, remove the old link */
+ if ( unlink(fscript->path) < 0 ) {
+ e_error(script->event,
+ "Failed to clean up after rename: "
+ "unlink(%s) failed: %m", fscript->path);
+ }
+
+ if ( script->name != NULL && *script->name != '\0' )
+ script->name = p_strdup(script->pool, newname);
+ fscript->path = p_strdup(script->pool, newpath);
+ fscript->filename = p_strdup(script->pool, newfile);
+ } else {
+ /* If something went wrong, remove the new link to restore previous
+ * state
+ */
+ if ( unlink(newpath) < 0 ) {
+ e_error(script->event,
+ "Failed to clean up after failed rename: "
+ "unlink(%s) failed: %m", newpath);
+ }
+ }
+ } else {
+ /* Our efforts failed right away */
+ switch ( errno ) {
+ case ENOENT:
+ sieve_script_set_error(script, SIEVE_ERROR_NOT_FOUND,
+ "Sieve script does not exist.");
+ break;
+ case EEXIST:
+ sieve_script_set_error(script, SIEVE_ERROR_EXISTS,
+ "A sieve script with that name already exists.");
+ break;
+ default:
+ sieve_script_set_critical(script,
+ "Failed to rename Sieve script: "
+ "link(%s, %s) failed: %m", fscript->path, newpath);
+ }
+ }
+ } T_END;
+
+ return ret;
+}
+
+/*
+ * Properties
+ */
+
+static int sieve_file_script_get_size
+(const struct sieve_script *script, uoff_t *size_r)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+ *size_r = fscript->st.st_size;
+ return 1;
+}
+
+const char *sieve_file_script_get_dirpath
+(const struct sieve_script *script)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+ if ( script->driver_name != sieve_file_script.driver_name )
+ return NULL;
+
+ return fscript->dirpath;
+}
+
+const char *sieve_file_script_get_path
+(const struct sieve_script *script)
+{
+ struct sieve_file_script *fscript = (struct sieve_file_script *)script;
+
+ if ( script->driver_name != sieve_file_script.driver_name )
+ return NULL;
+
+ return fscript->path;
+}
+
+/*
+ * Matching
+ */
+
+static bool sieve_file_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+ struct sieve_file_script *fscript =
+ (struct sieve_file_script *)script;
+ struct sieve_file_script *fother =
+ (struct sieve_file_script *)other;
+
+ return ( CMP_DEV_T(fscript->st.st_dev, fother->st.st_dev) &&
+ fscript->st.st_ino == fother->st.st_ino );
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_script sieve_file_script = {
+ .driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME,
+ .v = {
+ .open = sieve_file_script_open,
+
+ .get_stream = sieve_file_script_get_stream,
+
+ .binary_read_metadata = sieve_file_script_binary_read_metadata,
+ .binary_load = sieve_file_script_binary_load,
+ .binary_save = sieve_file_script_binary_save,
+ .binary_get_prefix = sieve_file_script_binary_get_prefix,
+
+ .rename = sieve_file_storage_script_rename,
+ .delete = sieve_file_storage_script_delete,
+ .is_active = sieve_file_storage_script_is_active,
+ .activate = sieve_file_storage_script_activate,
+
+ .get_size = sieve_file_script_get_size,
+
+ .equals = sieve_file_script_equals
+ }
+};
+
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-active.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-active.c
new file mode 100644
index 0000000..28bc9eb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-active.c
@@ -0,0 +1,403 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "path-util.h"
+#include "ioloop.h"
+#include "hostpid.h"
+#include "file-copy.h"
+#include "time-util.h"
+
+#include "sieve-file-storage.h"
+
+#include <unistd.h>
+
+/*
+ * Symlink manipulation
+ */
+
+static int sieve_file_storage_active_read_link
+(struct sieve_file_storage *fstorage, const char **link_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ const char *error = NULL;
+ int ret;
+
+ ret = t_readlink(fstorage->active_path, link_r, &error);
+
+ if ( ret < 0 ) {
+ *link_r = NULL;
+
+ if ( errno == EINVAL ) {
+ /* Our symlink is no symlink. Report 'no active script'.
+ * Activating a script will automatically resolve this, so
+ * there is no need to panic on this one.
+ */
+ if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 &&
+ (storage->flags & SIEVE_STORAGE_FLAG_SYNCHRONIZING) == 0 ) {
+ e_warning(storage->event,
+ "Active sieve script symlink %s is no symlink.",
+ fstorage->active_path);
+ }
+ return 0;
+ }
+
+ if ( errno == ENOENT ) {
+ /* Symlink not found */
+ return 0;
+ }
+
+ /* We do need to panic otherwise */
+ sieve_storage_set_critical(storage,
+ "Performing t_readlink() on active sieve symlink '%s' failed: %s",
+ fstorage->active_path, error);
+ return -1;
+ }
+
+ /* ret is now assured to be valid, i.e. > 0 */
+ return 1;
+}
+
+static const char *sieve_file_storage_active_parse_link
+(struct sieve_file_storage *fstorage, const char *link,
+ const char **scriptname_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ const char *fname, *scriptname, *scriptpath, *link_dir;
+
+ /* Split off directory from link path */
+ fname = strrchr(fstorage->active_path, '/');
+ if (fname == NULL)
+ link_dir = "";
+ else
+ link_dir = t_strdup_until(fstorage->active_path, fname+1);
+
+ /* Split link into path and filename */
+ fname = strrchr(link, '/');
+ if ( fname != NULL ) {
+ scriptpath = t_strdup_until(link, fname+1);
+ fname++;
+ } else {
+ scriptpath = "";
+ fname = link;
+ }
+
+ /* Check the script name */
+ scriptname = sieve_script_file_get_scriptname(fname);
+
+ /* Warn if link is deemed to be invalid */
+ if ( scriptname == NULL ) {
+ e_warning(storage->event,
+ "Active Sieve script symlink %s is broken: "
+ "Invalid scriptname (points to %s).",
+ fstorage->active_path, link);
+ return NULL;
+ }
+
+ /* Check whether the path is any good */
+ const char *error = NULL;
+ if ( t_normpath_to(scriptpath, link_dir, &scriptpath, &error) < 0 ) {
+ e_warning(storage->event,
+ "Failed to check active Sieve script symlink %s: "
+ "Failed to normalize path (points to %s): %s",
+ fstorage->active_path, scriptpath, error);
+ return NULL;
+ }
+ if ( strcmp(scriptpath, fstorage->path) != 0 ) {
+ e_warning(storage->event,
+ "Active sieve script symlink %s is broken: "
+ "Invalid/unknown path to storage (points to %s).",
+ fstorage->active_path, scriptpath);
+ return NULL;
+ }
+
+ if ( scriptname_r != NULL )
+ *scriptname_r = scriptname;
+
+ return fname;
+}
+
+int sieve_file_storage_active_replace_link
+(struct sieve_file_storage *fstorage, const char *link_path)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ const char *active_path_new;
+ struct timeval *tv, tv_now;
+ int ret = 0;
+
+ tv = &ioloop_timeval;
+
+ for (;;) {
+ /* First the new symlink is created with a different filename */
+ active_path_new = t_strdup_printf
+ ("%s-new.%s.P%sM%s.%s",
+ fstorage->active_path,
+ dec2str(tv->tv_sec), my_pid,
+ dec2str(tv->tv_usec), my_hostname);
+
+ ret = symlink(link_path, active_path_new);
+
+ if ( ret < 0 ) {
+ /* If link exists we try again later */
+ if ( errno == EEXIST ) {
+ /* Wait and try again - very unlikely */
+ sleep(2);
+ tv = &tv_now;
+ i_gettimeofday(&tv_now);
+ continue;
+ }
+
+ /* Other error, critical */
+ sieve_storage_set_critical(storage,
+ "Creating symlink() %s to %s failed: %m",
+ active_path_new, link_path);
+ return -1;
+ }
+
+ /* Link created */
+ break;
+ }
+
+ /* Replace the existing link. This activates the new script */
+ ret = rename(active_path_new, fstorage->active_path);
+
+ if ( ret < 0 ) {
+ /* Failed; created symlink must be deleted */
+ i_unlink(active_path_new);
+ sieve_storage_set_critical(storage,
+ "Performing rename() %s to %s failed: %m",
+ active_path_new, fstorage->active_path);
+ return -1;
+ }
+
+ return 1;
+}
+
+/*
+ * Active script properties
+ */
+
+int sieve_file_storage_active_script_get_file
+(struct sieve_file_storage *fstorage, const char **file_r)
+{
+ const char *link, *scriptfile;
+ int ret;
+
+ *file_r = NULL;
+
+ /* Read the active link */
+ if ( (ret=sieve_file_storage_active_read_link(fstorage, &link)) <= 0 )
+ return ret;
+
+ /* Parse the link */
+ scriptfile = sieve_file_storage_active_parse_link(fstorage, link, NULL);
+
+ if (scriptfile == NULL) {
+ /* Obviously, someone has been playing with our symlink:
+ * ignore this situation and report 'no active script'.
+ * Activation should fix this situation.
+ */
+ return 0;
+ }
+
+ *file_r = scriptfile;
+ return 1;
+}
+
+int sieve_file_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ const char *link;
+ int ret;
+
+ *name_r = NULL;
+
+ /* Read the active link */
+ if ( (ret=sieve_file_storage_active_read_link
+ (fstorage, &link)) <= 0 )
+ return ret;
+
+ if ( sieve_file_storage_active_parse_link
+ (fstorage, link, name_r) == NULL ) {
+ /* Obviously, someone has been playing with our symlink:
+ * ignore this situation and report 'no active script'.
+ * Activation should fix this situation.
+ */
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Active script
+ */
+
+struct sieve_script *sieve_file_storage_active_script_open
+(struct sieve_storage *storage)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct sieve_file_script *fscript;
+ const char *scriptfile, *link;
+ int ret;
+
+ sieve_storage_clear_error(storage);
+
+ /* Read the active link */
+ if ( (ret=sieve_file_storage_active_read_link(fstorage, &link)) <= 0 ) {
+ if ( ret < 0 )
+ return NULL;
+
+ /* Try to open the active_path as a regular file */
+ if ( S_ISDIR(fstorage->st.st_mode) ) {
+ fscript = sieve_file_script_open_from_path(fstorage,
+ fstorage->active_path, NULL, NULL);
+ } else {
+ fscript = sieve_file_script_open_from_name(fstorage, NULL);
+ }
+ if ( fscript == NULL ) {
+ if ( storage->error_code != SIEVE_ERROR_NOT_FOUND ) {
+ sieve_storage_set_critical(storage,
+ "Failed to open active path `%s' as regular file: %s",
+ fstorage->active_path, storage->error);
+ }
+ return NULL;
+ }
+
+ return &fscript->script;
+ }
+
+ /* Parse the link */
+ scriptfile = sieve_file_storage_active_parse_link(fstorage, link, NULL);
+ if (scriptfile == NULL) {
+ /* Obviously someone has been playing with our symlink,
+ * ignore this situation and report 'no active script'.
+ * Activation should fix this situation.
+ */
+ sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND,
+ "Active script is invalid");
+ return NULL;
+ }
+
+ fscript = sieve_file_script_open_from_path(fstorage,
+ fstorage->active_path,
+ sieve_script_file_get_scriptname(scriptfile),
+ NULL);
+ if ( fscript == NULL && storage->error_code == SIEVE_ERROR_NOT_FOUND ) {
+ e_warning(storage->event,
+ "Active sieve script symlink %s points to non-existent script "
+ "(points to %s).", fstorage->active_path, link);
+ }
+ return (fscript != NULL ? &fscript->script : NULL);
+}
+
+int sieve_file_storage_active_script_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct stat st;
+
+ /* Try direct lstat first */
+ if ( lstat(fstorage->active_path, &st) == 0 ) {
+ if ( !S_ISLNK(st.st_mode) ) {
+ *last_change_r = st.st_mtime;
+ return 0;
+ }
+ }
+ /* Check error */
+ else if ( errno != ENOENT ) {
+ sieve_storage_set_critical(storage,
+ "lstat(%s) failed: %m", fstorage->active_path);
+ }
+
+ /* Fall back to statting storage directory */
+ return sieve_storage_get_last_change(storage, last_change_r);
+}
+
+bool sieve_file_storage_active_rescue_regular
+(struct sieve_file_storage *fstorage)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct stat st;
+
+ /* Stat the file */
+ if ( lstat(fstorage->active_path, &st) != 0 ) {
+ if ( errno != ENOENT ) {
+ sieve_storage_set_critical(storage,
+ "Failed to stat active sieve script symlink (%s): %m.",
+ fstorage->active_path);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ if ( S_ISLNK( st.st_mode ) ) {
+ e_debug(storage->event,
+ "Nothing to rescue %s.", fstorage->active_path);
+ return TRUE; /* Nothing to rescue */
+ }
+
+ /* Only regular files can be rescued */
+ if ( S_ISREG( st.st_mode ) ) {
+ const char *dstpath;
+ bool result = TRUE;
+
+ T_BEGIN {
+
+ dstpath = t_strconcat( fstorage->path, "/",
+ sieve_script_file_from_name("dovecot.orig"), NULL );
+ if ( file_copy(fstorage->active_path, dstpath, TRUE) < 1 ) {
+ sieve_storage_set_critical(storage,
+ "Active sieve script file '%s' is a regular file "
+ "and copying it to the script storage as '%s' failed. "
+ "This needs to be fixed manually.",
+ fstorage->active_path, dstpath);
+ result = FALSE;
+ } else {
+ e_info(storage->event,
+ "Moved active sieve script file '%s' "
+ "to script storage as '%s'.",
+ fstorage->active_path, dstpath);
+ }
+ } T_END;
+
+ return result;
+ }
+
+ sieve_storage_set_critical(storage,
+ "Active sieve script file '%s' is no symlink nor a regular file. "
+ "This needs to be fixed manually.", fstorage->active_path);
+ return FALSE;
+}
+
+int sieve_file_storage_deactivate(struct sieve_storage *storage)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ int ret;
+
+ if ( sieve_file_storage_pre_modify(storage) < 0 )
+ return -1;
+
+ if ( !sieve_file_storage_active_rescue_regular(fstorage) )
+ return -1;
+
+ /* Delete the symlink, so no script is active */
+ ret = unlink(fstorage->active_path);
+
+ if ( ret < 0 ) {
+ if ( errno != ENOENT ) {
+ sieve_storage_set_critical(storage,
+ "Failed to deactivate Sieve: "
+ "unlink(%s) failed: %m", fstorage->active_path);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-list.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-list.c
new file mode 100644
index 0000000..75a34c9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-list.c
@@ -0,0 +1,140 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "eacces-error.h"
+
+#include "sieve-common.h"
+#include "sieve-script-private.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+
+struct sieve_file_list_context {
+ struct sieve_storage_list_context context;
+ pool_t pool;
+
+ const char *active;
+ const char *dir;
+ DIR *dirp;
+};
+
+struct sieve_storage_list_context *sieve_file_storage_list_init
+(struct sieve_storage *storage)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct sieve_file_list_context *flctx;
+ const char *active = NULL;
+ pool_t pool;
+ DIR *dirp;
+
+ /* Open the directory */
+ if ( (dirp = opendir(fstorage->path)) == NULL ) {
+ switch ( errno ) {
+ case ENOENT:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NOT_FOUND,
+ "Script storage not found");
+ break;
+ case EACCES:
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_PERMISSION,
+ "Script storage not accessible");
+ e_error(storage->event, "Failed to list scripts: %s",
+ eacces_error_get("opendir", fstorage->path));
+ break;
+ default:
+ sieve_storage_set_critical(storage,
+ "Failed to list scripts: "
+ "opendir(%s) failed: %m", fstorage->path);
+ break;
+ }
+ return NULL;
+ }
+
+ T_BEGIN {
+ /* Get the name of the active script */
+ if ( sieve_file_storage_active_script_get_file(fstorage, &active) < 0) {
+ flctx = NULL;
+ } else {
+ pool = pool_alloconly_create("sieve_file_list_context", 1024);
+ flctx = p_new(pool, struct sieve_file_list_context, 1);
+ flctx->pool = pool;
+ flctx->dirp = dirp;
+ flctx->active = ( active != NULL ? p_strdup(pool, active) : NULL );
+ }
+ } T_END;
+
+ if ( flctx == NULL ) {
+ if ( closedir(dirp) < 0) {
+ e_error(storage->event,
+ "closedir(%s) failed: %m", fstorage->path);
+ }
+ return NULL;
+ }
+ return &flctx->context;
+}
+
+const char *sieve_file_storage_list_next
+(struct sieve_storage_list_context *ctx, bool *active)
+{
+ struct sieve_file_list_context *flctx =
+ (struct sieve_file_list_context *)ctx;
+ const struct sieve_file_storage *fstorage =
+ (const struct sieve_file_storage *)ctx->storage;
+ struct dirent *dp;
+ const char *scriptname;
+
+ *active = FALSE;
+
+ for (;;) {
+ if ( (dp = readdir(flctx->dirp)) == NULL )
+ return NULL;
+
+ scriptname = sieve_script_file_get_scriptname(dp->d_name);
+ if (scriptname != NULL ) {
+ /* Don't list our active sieve script link if the link
+ * resides in the script dir (generally a bad idea).
+ */
+ i_assert( fstorage->link_path != NULL );
+ if ( *(fstorage->link_path) == '\0' &&
+ strcmp(fstorage->active_fname, dp->d_name) == 0 )
+ continue;
+
+ break;
+ }
+ }
+
+ if ( flctx->active != NULL && strcmp(dp->d_name, flctx->active) == 0 ) {
+ *active = TRUE;
+ flctx->active = NULL;
+ }
+
+ return scriptname;
+}
+
+int sieve_file_storage_list_deinit(struct sieve_storage_list_context *lctx)
+{
+ struct sieve_file_list_context *flctx =
+ (struct sieve_file_list_context *)lctx;
+ const struct sieve_file_storage *fstorage =
+ (const struct sieve_file_storage *)lctx->storage;
+
+ if (closedir(flctx->dirp) < 0) {
+ e_error(lctx->storage->event,
+ "closedir(%s) failed: %m", fstorage->path);
+ }
+
+ pool_unref(&flctx->pool);
+
+ // FIXME: return error here if something went wrong during listing
+ return 0;
+}
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-quota.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-quota.c
new file mode 100644
index 0000000..65e075d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-quota.c
@@ -0,0 +1,120 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+
+#include "sieve.h"
+#include "sieve-script.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int sieve_file_storage_quota_havespace
+(struct sieve_storage *storage, const char *scriptname, size_t size,
+ enum sieve_storage_quota *quota_r, uint64_t *limit_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct dirent *dp;
+ DIR *dirp;
+ uint64_t script_count = 1;
+ uint64_t script_storage = size;
+ int result = 1;
+
+ /* Open the directory */
+ if ( (dirp = opendir(fstorage->path)) == NULL ) {
+ sieve_storage_set_critical(storage,
+ "quota: opendir(%s) failed: %m", fstorage->path);
+ return -1;
+ }
+
+ /* Scan all files */
+ for (;;) {
+ const char *name;
+ bool replaced = FALSE;
+
+ /* Read next entry */
+ errno = 0;
+ if ( (dp = readdir(dirp)) == NULL ) {
+ if ( errno != 0 ) {
+ sieve_storage_set_critical(storage,
+ "quota: readdir(%s) failed: %m", fstorage->path);
+ result = -1;
+ }
+ break;
+ }
+
+ /* Parse filename */
+ name = sieve_script_file_get_scriptname(dp->d_name);
+
+ /* Ignore non-script files */
+ if ( name == NULL )
+ continue;
+
+ /* Don't list our active sieve script link if the link
+ * resides in the script dir (generally a bad idea).
+ */
+ i_assert( fstorage->link_path != NULL );
+ if ( *(fstorage->link_path) == '\0' &&
+ strcmp(fstorage->active_fname, dp->d_name) == 0 )
+ continue;
+
+ if ( strcmp(name, scriptname) == 0 )
+ replaced = TRUE;
+
+ /* Check count quota if necessary */
+ if ( storage->max_scripts > 0 ) {
+ if ( !replaced ) {
+ script_count++;
+
+ if ( script_count > storage->max_scripts ) {
+ *quota_r = SIEVE_STORAGE_QUOTA_MAXSCRIPTS;
+ *limit_r = storage->max_scripts;
+ result = 0;
+ break;
+ }
+ }
+ }
+
+ /* Check storage quota if necessary */
+ if ( storage->max_storage > 0 ) {
+ const char *path;
+ struct stat st;
+
+ path = t_strconcat(fstorage->path, "/", dp->d_name, NULL);
+
+ if ( stat(path, &st) < 0 ) {
+ e_warning(storage->event,
+ "quota: stat(%s) failed: %m", path);
+ continue;
+ }
+
+ if ( !replaced ) {
+ script_storage += st.st_size;
+
+ if ( script_storage > storage->max_storage ) {
+ *quota_r = SIEVE_STORAGE_QUOTA_MAXSTORAGE;
+ *limit_r = storage->max_storage;
+ result = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Close directory */
+ if ( closedir(dirp) < 0 ) {
+ sieve_storage_set_critical(storage,
+ "quota: closedir(%s) failed: %m", fstorage->path);
+ }
+ return result;
+}
+
+
+
+
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-save.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-save.c
new file mode 100644
index 0000000..bfbe380
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage-save.c
@@ -0,0 +1,544 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "array.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "eacces-error.h"
+#include "safe-mkstemp.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utime.h>
+
+struct sieve_file_save_context {
+ struct sieve_storage_save_context context;
+
+ pool_t pool;
+
+ struct ostream *output;
+ int fd;
+ const char *tmp_path;
+
+ time_t mtime;
+
+ bool failed:1;
+ bool finished:1;
+};
+
+static const char *sieve_generate_tmp_filename(const char *scriptname)
+{
+ static struct timeval last_tv = { 0, 0 };
+ struct timeval tv;
+
+ /* use secs + usecs to guarantee uniqueness within this process. */
+ if (ioloop_timeval.tv_sec > last_tv.tv_sec ||
+ (ioloop_timeval.tv_sec == last_tv.tv_sec &&
+ ioloop_timeval.tv_usec > last_tv.tv_usec)) {
+ tv = ioloop_timeval;
+ } else {
+ tv = last_tv;
+ if (++tv.tv_usec == 1000000) {
+ tv.tv_sec++;
+ tv.tv_usec = 0;
+ }
+ }
+ last_tv = tv;
+
+ if ( scriptname == NULL ) {
+ return t_strdup_printf("%s.M%sP%s.%s.tmp",
+ dec2str(tv.tv_sec), dec2str(tv.tv_usec),
+ my_pid, my_hostname);
+ }
+
+ scriptname = t_strdup_printf("%s_%s.M%sP%s.%s",
+ scriptname, dec2str(tv.tv_sec), dec2str(tv.tv_usec),
+ my_pid, my_hostname);
+ return sieve_script_file_from_name(scriptname);
+}
+
+static int sieve_file_storage_create_tmp
+(struct sieve_file_storage *fstorage, const char *scriptname,
+ const char **fpath_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct stat st;
+ unsigned int prefix_len;
+ const char *tmp_fname = NULL;
+ string_t *path;
+ int fd;
+
+ path = t_str_new(256);
+ str_append(path, fstorage->path);
+ str_append(path, "/tmp/");
+ prefix_len = str_len(path);
+
+ for (;;) {
+ tmp_fname = sieve_generate_tmp_filename(scriptname);
+ str_truncate(path, prefix_len);
+ str_append(path, tmp_fname);
+
+ /* stat() first to see if it exists. pretty much the only
+ possibility of that happening is if time had moved
+ backwards, but even then it's highly unlikely. */
+ if (stat(str_c(path), &st) == 0) {
+ /* try another file name */
+ } else if (errno != ENOENT) {
+ switch ( errno ) {
+ case EACCES:
+ sieve_storage_set_critical(storage, "save: %s",
+ eacces_error_get("stat", fstorage->path));
+ break;
+ default:
+ sieve_storage_set_critical(storage, "save: "
+ "stat(%s) failed: %m", str_c(path));
+ break;
+ }
+ return -1;
+ } else {
+ /* doesn't exist */
+ mode_t old_mask = umask(0777 & ~(fstorage->file_create_mode));
+ fd = open(str_c(path),
+ O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777);
+ umask(old_mask);
+
+ if (fd != -1 || errno != EEXIST)
+ break;
+ /* race condition between stat() and open().
+ highly unlikely. */
+ }
+ }
+
+ *fpath_r = str_c(path);
+ if (fd == -1) {
+ if (ENOQUOTA(errno)) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_QUOTA,
+ "Not enough disk quota");
+ } else {
+ switch ( errno ) {
+ case EACCES:
+ sieve_storage_set_critical(storage, "save: %s",
+ eacces_error_get("open", fstorage->path));
+ break;
+ default:
+ sieve_storage_set_critical(storage, "save: "
+ "open(%s) failed: %m", str_c(path));
+ break;
+ }
+ }
+ }
+
+ return fd;
+}
+
+static int sieve_file_storage_script_move
+(struct sieve_file_save_context *fsctx, const char *dst)
+{
+ struct sieve_storage_save_context *sctx = &fsctx->context;
+ struct sieve_storage *storage = sctx->storage;
+ int result = 0;
+
+ T_BEGIN {
+
+ /* Using rename() to ensure existing files are replaced
+ * without conflicts with other processes using the same
+ * file. The kernel wont fully delete the original until
+ * all processes have closed the file.
+ */
+ if (rename(fsctx->tmp_path, dst) == 0)
+ result = 0;
+ else {
+ result = -1;
+ if ( ENOQUOTA(errno) ) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_QUOTA,
+ "Not enough disk quota");
+ } else if ( errno == EACCES ) {
+ sieve_storage_set_critical(storage, "save: "
+ "Failed to save Sieve script: "
+ "%s", eacces_error_get("rename", dst));
+ } else {
+ sieve_storage_set_critical(storage, "save: "
+ "rename(%s, %s) failed: %m", fsctx->tmp_path, dst);
+ }
+ }
+
+ /* Always destroy temp file */
+ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+ e_warning(storage->event, "save: "
+ "unlink(%s) failed: %m", fsctx->tmp_path);
+ }
+ } T_END;
+
+ return result;
+}
+
+struct sieve_storage_save_context *
+sieve_file_storage_save_alloc(struct sieve_storage *storage)
+{
+ struct sieve_file_save_context *fsctx;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_file_save_context", 1024);
+ fsctx = p_new(pool, struct sieve_file_save_context, 1);
+ fsctx->context.pool = pool;
+ fsctx->context.storage = storage;
+
+ return &fsctx->context;
+}
+
+int sieve_file_storage_save_init(struct sieve_storage_save_context *sctx,
+ const char *scriptname, struct istream *input)
+{
+ struct sieve_storage *storage = sctx->storage;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+ pool_t pool = sctx->pool;
+ const char *path;
+ int fd, ret = 0;
+
+ if ( sieve_file_storage_pre_modify(storage) < 0 )
+ return -1;
+
+ if ( scriptname != NULL ) {
+ /* Prevent overwriting the active script link when it resides in the
+ * sieve storage directory.
+ */
+ i_assert( fstorage->link_path != NULL );
+ if ( *(fstorage->link_path) == '\0' ) {
+ const char *svext;
+ size_t namelen;
+
+ svext = strrchr(fstorage->active_fname, '.');
+ namelen = svext - fstorage->active_fname;
+ if ( svext != NULL && str_begins(svext+1, "sieve") &&
+ strlen(scriptname) == namelen &&
+ str_begins(fstorage->active_fname, scriptname) )
+ {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_BAD_PARAMS,
+ "Script name `%s' is reserved for internal use.",
+ scriptname);
+ return -1;
+ }
+ }
+ }
+
+ T_BEGIN {
+ fd = sieve_file_storage_create_tmp(fstorage, scriptname, &path);
+ if (fd == -1) {
+ ret = -1;
+ } else {
+ fsctx->context.scriptname = p_strdup(pool, scriptname);
+ fsctx->context.input = input;
+ fsctx->fd = fd;
+ fsctx->output = o_stream_create_fd(fsctx->fd, 0);
+ fsctx->tmp_path = p_strdup(pool, path);
+ }
+ } T_END;
+
+ return ret;
+}
+
+int sieve_file_storage_save_continue
+(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+
+ switch (o_stream_send_istream(fsctx->output, sctx->input)) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ sieve_storage_set_critical(sctx->storage,
+ "save: read(%s) failed: %s",
+ i_stream_get_name(sctx->input),
+ i_stream_get_error(sctx->input));
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ sieve_storage_set_critical(sctx->storage,
+ "save: write(%s) failed: %s", fsctx->tmp_path,
+ o_stream_get_error(fsctx->output));
+ return -1;
+ }
+ return 0;
+}
+
+int sieve_file_storage_save_finish
+(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+ struct sieve_storage *storage = sctx->storage;
+ int output_errno;
+
+ if ( sctx->failed && fsctx->fd == -1 ) {
+ /* tmp file creation failed */
+ return -1;
+ }
+
+ T_BEGIN {
+ output_errno = fsctx->output->stream_errno;
+ o_stream_destroy(&fsctx->output);
+
+ if ( fsync(fsctx->fd) < 0 ) {
+ sieve_storage_set_critical(storage, "save: "
+ "fsync(%s) failed: %m", fsctx->tmp_path);
+ sctx->failed = TRUE;
+ }
+ if ( close(fsctx->fd) < 0 ) {
+ sieve_storage_set_critical(storage, "save: "
+ "close(%s) failed: %m", fsctx->tmp_path);
+ sctx->failed = TRUE;
+ }
+ fsctx->fd = -1;
+
+ if ( sctx->failed ) {
+ /* delete the tmp file */
+ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+ e_warning(storage->event, "save: "
+ "unlink(%s) failed: %m",
+ fsctx->tmp_path);
+ }
+
+ errno = output_errno;
+ if ( ENOQUOTA(errno) ) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_QUOTA,
+ "Not enough disk quota");
+ } else if ( errno != 0 ) {
+ sieve_storage_set_critical(storage, "save: "
+ "write(%s) failed: %m", fsctx->tmp_path);
+ }
+ fsctx->tmp_path = NULL;
+ }
+ } T_END;
+
+ return ( sctx->failed ? -1 : 0 );
+}
+
+struct sieve_script *sieve_file_storage_save_get_tempscript
+(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)sctx->storage;
+ struct sieve_file_script *tmpscript;
+ enum sieve_error error;
+ const char *scriptname;
+
+ if (sctx->failed)
+ return NULL;
+
+ if ( sctx->scriptobject != NULL )
+ return sctx->scriptobject;
+
+ scriptname =
+ ( sctx->scriptname == NULL ? "" : sctx->scriptname );
+ tmpscript = sieve_file_script_open_from_path
+ (fstorage, fsctx->tmp_path, scriptname, &error);
+
+ if ( tmpscript == NULL ) {
+ if ( error == SIEVE_ERROR_NOT_FOUND ) {
+ sieve_storage_set_critical(sctx->storage, "save: "
+ "Temporary script file `%s' got lost, "
+ "which should not happen (possibly deleted externally).",
+ fsctx->tmp_path);
+ } else {
+ sieve_storage_set_critical(sctx->storage, "save: "
+ "Failed to open temporary script file `%s'",
+ fsctx->tmp_path);
+ }
+ return NULL;
+ }
+
+ return &tmpscript->script;
+}
+
+static void sieve_file_storage_update_mtime
+(struct sieve_storage *storage, const char *path, time_t mtime)
+{
+ struct utimbuf times = { .actime = mtime, .modtime = mtime };
+
+ if ( utime(path, &times) < 0 ) {
+ switch ( errno ) {
+ case ENOENT:
+ break;
+ case EACCES:
+ e_error(storage->event, "save: %s",
+ eacces_error_get("utime", path));
+ break;
+ default:
+ e_error(storage->event,
+ "save: utime(%s) failed: %m", path);
+ }
+ }
+}
+
+int sieve_file_storage_save_commit
+(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+ struct sieve_storage *storage = sctx->storage;
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)sctx->storage;
+ const char *dest_path;
+ bool failed = FALSE;
+
+ i_assert(fsctx->output == NULL);
+
+ T_BEGIN {
+ dest_path = t_strconcat(fstorage->path, "/",
+ sieve_script_file_from_name(sctx->scriptname), NULL);
+
+ failed = ( sieve_file_storage_script_move(fsctx, dest_path) < 0 );
+ if ( sctx->mtime != (time_t)-1 )
+ sieve_file_storage_update_mtime(storage, dest_path, sctx->mtime);
+ } T_END;
+
+ return ( failed ? -1 : 0 );
+}
+
+void sieve_file_storage_save_cancel(struct sieve_storage_save_context *sctx)
+{
+ struct sieve_file_save_context *fsctx =
+ (struct sieve_file_save_context *)sctx;
+ struct sieve_storage *storage = sctx->storage;
+
+ if (fsctx->tmp_path != NULL &&
+ unlink(fsctx->tmp_path) < 0 && errno != ENOENT) {
+ e_warning(storage->event, "save: unlink(%s) failed: %m",
+ fsctx->tmp_path);
+ }
+
+ i_assert(fsctx->output == NULL);
+}
+
+static int
+sieve_file_storage_save_to(struct sieve_file_storage *fstorage,
+ string_t *temp_path, struct istream *input,
+ const char *target)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct ostream *output;
+ int fd;
+
+ // FIXME: move this to base class
+ // FIXME: use io_stream_temp
+
+ fd = safe_mkstemp_hostpid
+ (temp_path, fstorage->file_create_mode, (uid_t)-1, (gid_t)-1);
+ if ( fd < 0 ) {
+ if ( errno == EACCES ) {
+ sieve_storage_set_critical(storage,
+ "Failed to create temporary file: %s",
+ eacces_error_get_creating("open", str_c(temp_path)));
+ } else {
+ sieve_storage_set_critical(storage,
+ "Failed to create temporary file: open(%s) failed: %m",
+ str_c(temp_path));
+ }
+ return -1;
+ }
+
+ output = o_stream_create_fd(fd, 0);
+ switch ( o_stream_send_istream(output, input) ) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ break;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ sieve_storage_set_critical(storage,
+ "read(%s) failed: %s", i_stream_get_name(input),
+ i_stream_get_error(input));
+ o_stream_destroy(&output);
+ i_unlink(str_c(temp_path));
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ sieve_storage_set_critical(storage,
+ "write(%s) failed: %s", str_c(temp_path),
+ o_stream_get_error(output));
+ o_stream_destroy(&output);
+ i_unlink(str_c(temp_path));
+ return -1;
+ }
+ o_stream_destroy(&output);
+
+ if ( rename(str_c(temp_path), target) < 0 ) {
+ if ( ENOQUOTA(errno) ) {
+ sieve_storage_set_error(storage,
+ SIEVE_ERROR_NO_QUOTA,
+ "Not enough disk quota");
+ } else if ( errno == EACCES ) {
+ sieve_storage_set_critical(storage,
+ "%s", eacces_error_get("rename", target));
+ } else {
+ sieve_storage_set_critical(storage,
+ "rename(%s, %s) failed: %m",
+ str_c(temp_path), target);
+ }
+ i_unlink(str_c(temp_path));
+ }
+ return 0;
+}
+
+int sieve_file_storage_save_as
+(struct sieve_storage *storage, struct istream *input,
+ const char *name)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ string_t *temp_path;
+ const char *dest_path;
+
+ temp_path = t_str_new(256);
+ str_append(temp_path, fstorage->path);
+ str_append(temp_path, "/tmp/");
+ str_append(temp_path, sieve_script_file_from_name(name));
+ str_append_c(temp_path, '.');
+
+ dest_path = t_strconcat(fstorage->path, "/",
+ sieve_script_file_from_name(name), NULL);
+
+ return sieve_file_storage_save_to
+ (fstorage, temp_path, input, dest_path);
+}
+
+int sieve_file_storage_save_as_active
+(struct sieve_storage *storage, struct istream *input,
+ time_t mtime)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ string_t *temp_path;
+
+ temp_path = t_str_new(256);
+ str_append(temp_path, fstorage->active_path);
+ str_append_c(temp_path, '.');
+
+ if ( sieve_file_storage_save_to
+ (fstorage, temp_path, input, fstorage->active_path) < 0 )
+ return -1;
+
+ sieve_file_storage_update_mtime
+ (storage, fstorage->active_path, mtime);
+ return 0;
+}
+
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
new file mode 100644
index 0000000..76b3ebf
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.c
@@ -0,0 +1,918 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "path-util.h"
+#include "home-expand.h"
+#include "ioloop.h"
+#include "mkdir-parents.h"
+#include "eacces-error.h"
+#include "unlink-old-files.h"
+#include "mail-storage-private.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-error-private.h"
+#include "sieve-settings.h"
+
+#include "sieve-file-storage.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <utime.h>
+#include <sys/time.h>
+
+
+#define MAX_DIR_CREATE_MODE 0770
+
+/*
+ * Utility
+ */
+
+const char *sieve_file_storage_path_extend
+(struct sieve_file_storage *fstorage, const char *filename)
+{
+ const char *path = fstorage->path;
+
+ if ( path[strlen(path)-1] == '/' )
+ return t_strconcat(path, filename, NULL);
+
+ return t_strconcat(path, "/", filename , NULL);
+}
+
+/*
+ *
+ */
+
+static int sieve_file_storage_stat
+(struct sieve_file_storage *fstorage, const char *path,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct stat st;
+ const char *abspath, *error;
+
+ if ( lstat(path, &st) == 0 ) {
+ fstorage->lnk_st = st;
+
+ if ( !S_ISLNK(st.st_mode) || stat(path, &st) == 0 ) {
+ fstorage->st = st;
+ return 0;
+ }
+ }
+
+ switch ( errno ) {
+ case ENOENT:
+ if (t_abspath(path, &abspath, &error) < 0) {
+ sieve_storage_set_critical(storage,
+ "t_abspath(%s) failed: %s", path, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ break;
+ }
+ e_debug(storage->event, "Storage path `%s' not found", abspath);
+ sieve_storage_set_internal_error(storage); // should be overriden
+ *error_r = SIEVE_ERROR_NOT_FOUND;
+ break;
+ case EACCES:
+ sieve_storage_set_critical(storage,
+ "Failed to stat sieve storage path: %s",
+ eacces_error_get("stat", path));
+ *error_r = SIEVE_ERROR_NO_PERMISSION;
+ break;
+ default:
+ sieve_storage_set_critical(storage,
+ "Failed to stat sieve storage path: "
+ "stat(%s) failed: %m", path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ break;
+ }
+
+ return -1;
+}
+
+static const char *sieve_storage_get_relative_link_path
+ (const char *active_path, const char *storage_dir)
+{
+ const char *link_path, *p;
+ size_t pathlen;
+
+ /* Determine to what extent the sieve storage and active script
+ * paths match up. This enables the managed symlink to be short and the
+ * sieve storages can be moved around without trouble (if the active
+ * script path is common to the script storage).
+ */
+ p = strrchr(active_path, '/');
+ if ( p == NULL ) {
+ link_path = storage_dir;
+ } else {
+ pathlen = p - active_path;
+
+ if ( strncmp( storage_dir, active_path, pathlen ) == 0 &&
+ (storage_dir[pathlen] == '/' || storage_dir[pathlen] == '\0') )
+ {
+ if ( storage_dir[pathlen] == '\0' )
+ link_path = "";
+ else
+ link_path = storage_dir + pathlen + 1;
+ } else
+ link_path = storage_dir;
+ }
+
+ /* Add trailing '/' when link path is not empty
+ */
+ pathlen = strlen(link_path);
+ if ( pathlen != 0 && link_path[pathlen-1] != '/')
+ return t_strconcat(link_path, "/", NULL);
+
+ return t_strdup(link_path);
+}
+
+static mode_t get_dir_mode(mode_t mode)
+{
+ /* Add the execute bit if either read or write bit is set */
+
+ if ((mode & 0600) != 0) mode |= 0100;
+ if ((mode & 0060) != 0) mode |= 0010;
+ if ((mode & 0006) != 0) mode |= 0001;
+
+ return mode;
+}
+
+static int mkdir_verify
+(struct sieve_storage *storage, const char *dir,
+ mode_t mode, gid_t gid, const char *gid_origin)
+{
+ struct stat st;
+
+ if ( stat(dir, &st) == 0 )
+ return 0;
+
+ if ( errno == EACCES ) {
+ e_error(storage->event, "mkdir_verify: %s",
+ eacces_error_get("stat", dir));
+ return -1;
+ } else if ( errno != ENOENT ) {
+ e_error(storage->event, "mkdir_verify: "
+ "stat(%s) failed: %m", dir);
+ return -1;
+ }
+
+ if ( mkdir_parents_chgrp(dir, mode, gid, gid_origin) == 0 ) {
+ e_debug(storage->event, "Created storage directory %s", dir);
+ return 0;
+ }
+
+ switch ( errno ) {
+ case EEXIST:
+ return 0;
+ case ENOENT:
+ e_error(storage->event,
+ "Storage was deleted while it was being created");
+ break;
+ case EACCES:
+ e_error(storage->event, "%s",
+ eacces_error_get_creating("mkdir_parents_chgrp", dir));
+ break;
+ default:
+ e_error(storage->event,
+ "mkdir_parents_chgrp(%s) failed: %m", dir);
+ break;
+ }
+
+ return -1;
+}
+
+static int check_tmp(struct sieve_storage *storage, const char *path)
+{
+ struct stat st;
+
+ /* If tmp/ directory exists, we need to clean it up once in a while */
+ if ( stat(path, &st) < 0 ) {
+ if ( errno == ENOENT )
+ return 0;
+ if ( errno == EACCES ) {
+ e_error(storage->event, "check_tmp: %s",
+ eacces_error_get("stat", path));
+ return -1;
+ }
+ e_error(storage->event, "check_tmp: stat(%s) failed: %m", path);
+ return -1;
+ }
+
+ if ( st.st_atime > st.st_ctime + SIEVE_FILE_STORAGE_TMP_DELETE_SECS ) {
+ /* The directory should be empty. we won't do anything
+ until ctime changes. */
+ } else if ( st.st_atime < ioloop_time - SIEVE_FILE_STORAGE_TMP_SCAN_SECS ) {
+ /* Time to scan */
+ (void)unlink_old_files(path, "",
+ ioloop_time - SIEVE_FILE_STORAGE_TMP_DELETE_SECS);
+ }
+ return 1;
+}
+
+static struct sieve_storage *sieve_file_storage_alloc(void)
+{
+ struct sieve_file_storage *fstorage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_file_storage", 2048);
+ fstorage = p_new(pool, struct sieve_file_storage, 1);
+ fstorage->storage = sieve_file_storage;
+ fstorage->storage.pool = pool;
+
+ return &fstorage->storage;
+}
+
+static int sieve_file_storage_get_full_path
+(struct sieve_file_storage *fstorage, const char **storage_path,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *path = *storage_path;
+
+ /* Get full storage path */
+
+ if ( path != NULL &&
+ ((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
+ (((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/')) ) {
+ /* home-relative path. change to absolute. */
+ const char *home = sieve_environment_get_homedir(svinst);
+
+ if ( home != NULL ) {
+ if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
+ path = home_expand_tilde(path, home);
+ else
+ path = t_strconcat(home, "/", path, NULL);
+ } else {
+ sieve_storage_set_critical(storage,
+ "Sieve storage path `%s' is relative to home directory, "
+ "but home directory is not available.", path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ }
+ *storage_path = path;
+ return 0;
+}
+
+static int sieve_file_storage_get_full_active_path
+(struct sieve_file_storage *fstorage, const char **active_path,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *path = *active_path;
+
+ if ( path != NULL && *path != '\0' &&
+ ((path[0] == '~' && (path[1] == '/' || path[1] == '\0')) ||
+ (((svinst->flags & SIEVE_FLAG_HOME_RELATIVE) != 0 ) && path[0] != '/'))
+ ) {
+ /* home-relative path. change to absolute. */
+ const char *home = sieve_environment_get_homedir(svinst);
+
+ if ( home != NULL ) {
+ if ( path[0] == '~' && (path[1] == '/' || path[1] == '\0') )
+ path = home_expand_tilde(path, home);
+ else
+ path = t_strconcat(home, "/", path, NULL);
+ } else {
+ sieve_storage_set_critical(storage,
+ "Sieve storage active script path `%s' is relative to home directory, "
+ "but home directory is not available.", path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ }
+ *active_path = path;
+ return 0;
+}
+
+static int sieve_file_storage_init_common
+(struct sieve_file_storage *fstorage, const char *active_path,
+ const char *storage_path, bool exists, enum sieve_error *error_r)
+ ATTR_NULL(2, 3)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ const char *tmp_dir, *link_path, *active_fname, *storage_dir, *error;
+ bool have_link = FALSE;
+ int ret;
+
+ i_assert( storage_path != NULL || active_path != NULL );
+
+ fstorage->prev_mtime = (time_t)-1;
+
+ /* Get active script path */
+
+ if ( sieve_file_storage_get_full_active_path
+ (fstorage, &active_path, error_r) < 0 )
+ return -1;
+
+ /* Get the filename for the active script link */
+
+ active_fname = NULL;
+ if ( active_path != NULL && *active_path != '\0' ) {
+ const char *active_dir;
+
+ active_fname = strrchr(active_path, '/');
+ if ( active_fname == NULL ) {
+ active_fname = active_path;
+ active_dir = "";
+ } else {
+ active_dir = t_strdup_until(active_path, active_fname);
+ active_fname++;
+ }
+
+ if ( *active_fname == '\0' ) {
+ /* Link cannot be just a path ending in '/' */
+ sieve_storage_set_critical(storage,
+ "Path to %sscript must include the filename (path=%s)",
+ ( storage_path != NULL ? "active link/" : "" ),
+ active_path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ if (t_realpath(active_dir, &active_dir, &error) < 0) {
+ if (errno != ENOENT) {
+ e_error(storage->event,
+ "Failed to normalize active script directory "
+ "(path=%s): %s", active_dir, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ e_debug(storage->event,
+ "Failed to normalize active script directory "
+ "(path=%s): "
+ "Part of the path does not exist (yet)",
+ active_dir);
+ } else {
+ active_path = t_abspath_to(active_fname, active_dir);
+ }
+
+ e_debug(storage->event, "Using %sSieve script path: %s",
+ (storage_path != NULL ? "active " : ""), active_path);
+
+ fstorage->active_path = p_strdup(storage->pool, active_path);
+ fstorage->active_fname = p_strdup(storage->pool, active_fname);
+ }
+
+ /* Determine storage path */
+
+ storage_dir = storage_path;
+ if ( storage_path != NULL && *storage_path != '\0' ) {
+ e_debug(storage->event, "Using script storage path: %s",
+ storage_path);
+ have_link = TRUE;
+
+ } else {
+ if ((storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+ sieve_storage_set_critical(storage,
+ "Storage path cannot be empty for write access");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ storage_path = active_path;
+ }
+
+ i_assert(storage_path != NULL);
+
+ /* Prepare for write access */
+
+ if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+ mode_t dir_create_mode, file_create_mode;
+ gid_t file_create_gid;
+ const char *file_create_gid_origin;
+
+ /* Use safe permission defaults */
+ file_create_mode = 0600;
+ dir_create_mode = 0700;
+ file_create_gid = (gid_t)-1;
+ file_create_gid_origin = "defaults";
+
+ /* Get actual permissions */
+ if ( exists ) {
+ file_create_mode = (fstorage->st.st_mode & 0666) | 0600;
+ dir_create_mode = (fstorage->st.st_mode & 0777) | 0700;
+ file_create_gid_origin = storage_dir;
+
+ if ( !S_ISDIR(fstorage->st.st_mode) ) {
+ /* We're getting permissions from a file.
+ Apply +x modes as necessary. */
+ dir_create_mode = get_dir_mode(dir_create_mode);
+ }
+
+ if (S_ISDIR(fstorage->st.st_mode) &&
+ (fstorage->st.st_mode & S_ISGID) != 0) {
+ /* Directory's GID is used automatically for new files */
+ file_create_gid = (gid_t)-1;
+ } else if ((fstorage->st.st_mode & 0070) >> 3 ==
+ (fstorage->st.st_mode & 0007)) {
+ /* Group has same permissions as world, so don't bother changing it */
+ file_create_gid = (gid_t)-1;
+ } else if (getegid() == fstorage->st.st_gid) {
+ /* Using our own gid, no need to change it */
+ file_create_gid = (gid_t)-1;
+ } else {
+ file_create_gid = fstorage->st.st_gid;
+ }
+ }
+
+ e_debug(storage->event,
+ "Using permissions from %s: mode=0%o gid=%ld",
+ file_create_gid_origin, (int)dir_create_mode,
+ file_create_gid == (gid_t)-1 ?
+ -1L : (long)file_create_gid);
+
+ /*
+ * Ensure sieve local directory structure exists (full autocreate):
+ * This currently only consists of a ./tmp direcory
+ */
+
+ tmp_dir = t_strconcat(storage_path, "/tmp", NULL);
+
+ /* Try to find and clean up tmp dir */
+ if ( (ret=check_tmp(storage, tmp_dir)) < 0 ) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ /* Auto-create if necessary */
+ if ( ret == 0 && mkdir_verify(storage, tmp_dir,
+ dir_create_mode, file_create_gid, file_create_gid_origin) < 0 ) {
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ fstorage->dir_create_mode = dir_create_mode;
+ fstorage->file_create_mode = file_create_mode;
+ fstorage->file_create_gid = file_create_gid;
+ }
+
+ if ( !exists && sieve_file_storage_stat
+ (fstorage, storage_path, error_r) < 0 )
+ return -1;
+
+ if ( have_link ) {
+ if ( t_realpath(storage_path, &storage_path, &error) < 0 ) {
+ e_error(storage->event,
+ "Failed to normalize storage path (path=%s): %s",
+ storage_path, error);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ if ( active_path != NULL && *active_path != '\0' ) {
+ /* Get the path to be prefixed to the script name in the symlink
+ * pointing to the active script.
+ */
+ link_path = sieve_storage_get_relative_link_path
+ (fstorage->active_path, storage_path);
+
+ e_debug(storage->event,
+ "Relative path to sieve storage in active link: %s",
+ link_path);
+
+ fstorage->link_path = p_strdup(storage->pool, link_path);
+ }
+ }
+
+ fstorage->path = p_strdup(storage->pool, storage_path);
+ storage->location = fstorage->path;
+
+ return 0;
+}
+
+static int sieve_file_storage_init
+(struct sieve_storage *storage, const char *const *options,
+ enum sieve_error *error_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ const char *storage_path = storage->location;
+ const char *active_path = "";
+ bool exists = FALSE;
+
+ if ( options != NULL ) {
+ while ( *options != NULL ) {
+ const char *option = *options;
+
+ if ( strncasecmp(option, "active=", 7) == 0 && option[7] != '\0' ) {
+ active_path = option+7;
+ } else {
+ sieve_storage_set_critical(storage,
+ "Invalid option `%s'", option);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ options++;
+ }
+ }
+
+ /* Get full storage path */
+
+ if ( sieve_file_storage_get_full_path
+ (fstorage, &storage_path, error_r) < 0 )
+ return -1;
+
+ /* Stat storage directory */
+
+ if ( storage_path != NULL && *storage_path != '\0' ) {
+ if ( sieve_file_storage_stat(fstorage, storage_path, error_r) < 0 ) {
+ if ( *error_r != SIEVE_ERROR_NOT_FOUND )
+ return -1;
+ if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 ) {
+ /* For backwards compatibility, recognize when storage directory
+ does not exist while active script exists and is a regular
+ file. */
+ if ( active_path == NULL || *active_path == '\0' )
+ return -1;
+ if ( sieve_file_storage_get_full_active_path
+ (fstorage, &active_path, error_r) < 0 )
+ return -1;
+ if ( sieve_file_storage_stat
+ (fstorage, active_path, error_r) < 0 )
+ return -1;
+ if ( !S_ISREG(fstorage->lnk_st.st_mode) )
+ return -1;
+ e_debug(storage->event,
+ "Sieve storage path `%s' not found, "
+ "but the active script `%s' is a regular file, "
+ "so this is used for backwards compatibility.",
+ storage_path, active_path);
+ storage_path = NULL;
+ }
+ } else {
+ exists = TRUE;
+
+ if ( !S_ISDIR(fstorage->st.st_mode) ) {
+ if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+ sieve_storage_set_critical(storage,
+ "Sieve storage path `%s' is not a directory, "
+ "but it is to be opened for write access", storage_path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ if ( active_path != NULL && *active_path != '\0' ) {
+ e_warning(storage->event,
+ "Explicitly specified active script path `%s' is ignored; "
+ "storage path `%s' is not a directory",
+ active_path, storage_path);
+ }
+ active_path = storage_path;
+ storage_path = NULL;
+ }
+ }
+ }
+
+ if ( active_path == NULL || *active_path == '\0' ) {
+ if ( storage->main_storage ||
+ (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+ e_debug(storage->event,
+ "Active script path is unconfigured; "
+ "using default (path=%s)",
+ SIEVE_FILE_DEFAULT_PATH);
+ active_path = SIEVE_FILE_DEFAULT_PATH;
+ }
+ }
+
+ return sieve_file_storage_init_common
+ (fstorage, active_path, storage_path, exists, error_r);
+}
+
+static void sieve_file_storage_autodetect
+(struct sieve_file_storage *fstorage, const char **storage_path_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *home = sieve_environment_get_homedir(svinst);
+ int mode = ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ?
+ R_OK|W_OK|X_OK : R_OK|X_OK );
+
+ e_debug(storage->event, "Performing auto-detection");
+
+ /* We'll need to figure out the storage location ourself.
+ *
+ * It's $HOME/sieve or /sieve when (presumed to be) chrooted.
+ */
+ if ( home != NULL && *home != '\0' ) {
+ /* Use default ~/sieve */
+ e_debug(storage->event, "Use home (%s)", home);
+ *storage_path_r = t_strconcat(home, "/sieve", NULL);
+ } else {
+ e_debug(storage->event, "HOME is not set");
+
+ if (access("/sieve", mode) == 0) {
+ *storage_path_r = "/sieve";
+ e_debug(storage->event,
+ "Directory `/sieve' exists, assuming chroot");
+ }
+ }
+}
+
+static int sieve_file_storage_do_init_legacy
+(struct sieve_file_storage *fstorage, const char *active_path,
+ const char *storage_path, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = &fstorage->storage;
+ bool explicit = FALSE, exists = FALSE;
+
+ if ( storage_path == NULL || *storage_path == '\0' ) {
+ /* Try autodectection */
+ sieve_file_storage_autodetect(fstorage, &storage_path);
+
+ if ( storage_path != NULL && *storage_path != '\0') {
+ /* Got something: stat it */
+ if (sieve_file_storage_stat
+ (fstorage, storage_path, error_r) < 0 ) {
+ if (*error_r != SIEVE_ERROR_NOT_FOUND) {
+ /* Error */
+ return -1;
+ }
+ } else if ( S_ISDIR(fstorage->st.st_mode) ) {
+ /* Success */
+ exists = TRUE;
+ }
+ }
+
+ if ( (storage_path == NULL || *storage_path == '\0') &&
+ (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 ) {
+ sieve_storage_set_critical(storage,
+ "Could not find storage root directory for write access; "
+ "path was left unconfigured and autodetection failed");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ } else {
+ /* Get full storage path */
+ if ( sieve_file_storage_get_full_path
+ (fstorage, &storage_path, error_r) < 0 )
+ return -1;
+
+ /* Stat storage directory */
+ if ( sieve_file_storage_stat(fstorage, storage_path, error_r) < 0 ) {
+ if ( (*error_r != SIEVE_ERROR_NOT_FOUND) )
+ return -1;
+ if ( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+ storage_path = NULL;
+ } else {
+ exists = TRUE;
+ }
+
+ /* Storage path must be a directory */
+ if ( exists && !S_ISDIR(fstorage->st.st_mode) ) {
+ sieve_storage_set_critical(storage,
+ "Sieve storage path `%s' configured using sieve_dir "
+ "is not a directory", storage_path);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ explicit = TRUE;
+ }
+
+ if ( (active_path == NULL || *active_path == '\0') ) {
+ if ( storage->main_storage ||
+ (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0) {
+ e_debug(storage->event,
+ "Active script path is unconfigured; "
+ "using default (path=%s)",
+ SIEVE_FILE_DEFAULT_PATH);
+ active_path = SIEVE_FILE_DEFAULT_PATH;
+ } else {
+ return -1;
+ }
+ }
+
+ if ( !explicit && !exists &&
+ active_path != NULL && *active_path != '\0' &&
+ (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) == 0 )
+ storage_path = NULL;
+
+ if ( sieve_file_storage_init_common
+ (fstorage, active_path, storage_path, exists, error_r) < 0 )
+ return -1;
+ return 0;
+}
+
+struct sieve_storage *sieve_file_storage_init_legacy
+(struct sieve_instance *svinst, const char *active_path,
+ const char *storage_path, enum sieve_storage_flags flags,
+ enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ struct sieve_file_storage *fstorage;
+
+ storage = sieve_storage_alloc(svinst, NULL, &sieve_file_storage,
+ "", flags, TRUE);
+ fstorage = (struct sieve_file_storage *)storage;
+
+ T_BEGIN {
+ if ( sieve_file_storage_do_init_legacy
+ (fstorage, active_path, storage_path, error_r) < 0 ) {
+ sieve_storage_unref(&storage);
+ storage = NULL;
+ }
+ } T_END;
+
+ return storage;
+}
+
+struct sieve_file_storage *sieve_file_storage_init_from_path
+(struct sieve_instance *svinst, const char *path,
+ enum sieve_storage_flags flags, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage;
+ struct sieve_file_storage *fstorage;
+
+ i_assert( path != NULL );
+
+ storage = sieve_storage_alloc(svinst, NULL, &sieve_file_storage,
+ "", flags, FALSE);
+ fstorage = (struct sieve_file_storage *)storage;
+
+ T_BEGIN {
+ if ( sieve_file_storage_init_common
+ (fstorage, path, NULL, FALSE, error_r) < 0 ) {
+ sieve_storage_unref(&storage);
+ fstorage = NULL;
+ }
+ } T_END;
+
+ return fstorage;
+}
+
+static int sieve_file_storage_is_singular
+(struct sieve_storage *storage)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct stat st;
+
+ if ( fstorage->active_path == NULL )
+ return 1;
+
+ /* Stat the file */
+ if ( lstat(fstorage->active_path, &st) != 0 ) {
+ if ( errno != ENOENT ) {
+ sieve_storage_set_critical(storage,
+ "Failed to stat active sieve script symlink (%s): %m.",
+ fstorage->active_path);
+ return -1;
+ }
+ return 0;
+ }
+
+ if ( S_ISLNK( st.st_mode ) )
+ return 0;
+ if ( !S_ISREG( st.st_mode ) ) {
+ sieve_storage_set_critical(storage,
+ "Active sieve script file '%s' is no symlink nor a regular file.",
+ fstorage->active_path);
+ return -1;
+ }
+ return 1;
+}
+
+/*
+ *
+ */
+
+static int sieve_file_storage_get_last_change
+(struct sieve_storage *storage, time_t *last_change_r)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct stat st;
+
+ if ( fstorage->prev_mtime == (time_t)-1 ) {
+ /* Get the storage mtime before we modify it ourself */
+ if ( stat(fstorage->path, &st) < 0 ) {
+ if ( errno != ENOENT ) {
+ e_error(storage->event,
+ "stat(%s) failed: %m",
+ fstorage->path);
+ return -1;
+ }
+ st.st_mtime = 0;
+ }
+
+ fstorage->prev_mtime = st.st_mtime;
+ }
+
+ if ( last_change_r != NULL )
+ *last_change_r = fstorage->prev_mtime;
+ return 0;
+}
+
+int sieve_file_storage_pre_modify
+(struct sieve_storage *storage)
+{
+ i_assert( (storage->flags & SIEVE_STORAGE_FLAG_READWRITE) != 0 );
+
+ return sieve_storage_get_last_change(storage, NULL);
+}
+
+static void sieve_file_storage_set_modified
+(struct sieve_storage *storage, time_t mtime)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct utimbuf times;
+ time_t cur_mtime;
+
+ if ( mtime != (time_t)-1 ) {
+ if ( sieve_storage_get_last_change(storage, &cur_mtime) >= 0 &&
+ cur_mtime > mtime )
+ return;
+ } else {
+ mtime = ioloop_time;
+ }
+
+ times.actime = mtime;
+ times.modtime = mtime;
+ if ( utime(fstorage->path, &times) < 0 ) {
+ switch ( errno ) {
+ case ENOENT:
+ break;
+ case EACCES:
+ e_error(storage->event, "%s",
+ eacces_error_get("utime", fstorage->path));
+ break;
+ default:
+ e_error(storage->event,
+ "utime(%s) failed: %m", fstorage->path);
+ }
+ } else {
+ fstorage->prev_mtime = mtime;
+ }
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_file_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+ struct sieve_file_storage *fstorage =
+ (struct sieve_file_storage *)storage;
+ struct sieve_file_script *fscript;
+
+ T_BEGIN {
+ fscript = sieve_file_script_init_from_name(fstorage, name);
+ } T_END;
+
+ return &fscript->script;
+}
+
+/*
+ * Driver definition
+ */
+
+const struct sieve_storage sieve_file_storage = {
+ .driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME,
+ .version = 0,
+ .allows_synchronization = TRUE,
+ .v = {
+ .alloc = sieve_file_storage_alloc,
+ .init = sieve_file_storage_init,
+
+ .get_last_change = sieve_file_storage_get_last_change,
+ .set_modified = sieve_file_storage_set_modified,
+
+ .is_singular = sieve_file_storage_is_singular,
+
+ .get_script = sieve_file_storage_get_script,
+
+ .get_script_sequence = sieve_file_storage_get_script_sequence,
+ .script_sequence_next = sieve_file_script_sequence_next,
+ .script_sequence_destroy = sieve_file_script_sequence_destroy,
+
+ .active_script_get_name = sieve_file_storage_active_script_get_name,
+ .active_script_open = sieve_file_storage_active_script_open,
+ .deactivate = sieve_file_storage_deactivate,
+ .active_script_get_last_change =
+ sieve_file_storage_active_script_get_last_change,
+
+ .list_init = sieve_file_storage_list_init,
+ .list_next = sieve_file_storage_list_next,
+ .list_deinit = sieve_file_storage_list_deinit,
+
+ .save_alloc = sieve_file_storage_save_alloc,
+ .save_init = sieve_file_storage_save_init,
+ .save_continue = sieve_file_storage_save_continue,
+ .save_finish = sieve_file_storage_save_finish,
+ .save_get_tempscript = sieve_file_storage_save_get_tempscript,
+ .save_cancel = sieve_file_storage_save_cancel,
+ .save_commit = sieve_file_storage_save_commit,
+ .save_as = sieve_file_storage_save_as,
+ .save_as_active = sieve_file_storage_save_as_active,
+
+ .quota_havespace = sieve_file_storage_quota_havespace
+ }
+};
diff --git a/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
new file mode 100644
index 0000000..ea7b92f
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/file/sieve-file-storage.h
@@ -0,0 +1,186 @@
+#ifndef SIEVE_FILE_STORAGE_H
+#define SIEVE_FILE_STORAGE_H
+
+#include "lib.h"
+#include "mail-user.h"
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define SIEVE_FILE_READ_BLOCK_SIZE (1024*8)
+
+#define SIEVE_FILE_DEFAULT_PATH "~/.dovecot."SIEVE_SCRIPT_FILEEXT
+
+/* How often to scan tmp/ directory for old files (based on dir's atime) */
+#define SIEVE_FILE_STORAGE_TMP_SCAN_SECS (8*60*60)
+/* Delete files having ctime older than this from tmp/. 36h is standard. */
+#define SIEVE_FILE_STORAGE_TMP_DELETE_SECS (36*60*60)
+
+/*
+ * Storage class
+ */
+
+struct sieve_file_storage {
+ struct sieve_storage storage;
+
+ const char *path;
+ const char *active_path;
+ const char *active_fname;
+ const char *link_path;
+
+ struct stat st;
+ struct stat lnk_st;
+
+ mode_t dir_create_mode;
+ mode_t file_create_mode;
+ gid_t file_create_gid;
+
+ time_t prev_mtime;
+};
+
+const char *sieve_file_storage_path_extend
+ (struct sieve_file_storage *fstorage, const char *filename);
+
+struct sieve_file_storage *sieve_file_storage_init_from_path
+(struct sieve_instance *svinst, const char *path,
+ enum sieve_storage_flags flags, enum sieve_error *error_r)
+ ATTR_NULL(4);
+
+int sieve_file_storage_pre_modify
+ (struct sieve_storage *storage);
+
+/* Active script */
+
+int sieve_file_storage_active_replace_link
+ (struct sieve_file_storage *fstorage, const char *link_path);
+bool sieve_file_storage_active_rescue_regular
+ (struct sieve_file_storage *fstorage);
+
+int sieve_file_storage_active_script_get_name
+ (struct sieve_storage *storage, const char **name_r);
+struct sieve_script *sieve_file_storage_active_script_open
+ (struct sieve_storage *storage);
+
+int sieve_file_storage_active_script_get_file
+ (struct sieve_file_storage *fstorage, const char **file_r);
+int sieve_file_storage_active_script_is_no_link
+ (struct sieve_file_storage *fstorage);
+
+int sieve_file_storage_deactivate
+ (struct sieve_storage *storage);
+
+int sieve_file_storage_active_script_get_last_change
+ (struct sieve_storage *storage, time_t *last_change_r);
+
+/* Listing */
+
+struct sieve_storage_list_context *sieve_file_storage_list_init
+ (struct sieve_storage *storage);
+const char *sieve_file_storage_list_next
+ (struct sieve_storage_list_context *ctx, bool *active);
+int sieve_file_storage_list_deinit
+ (struct sieve_storage_list_context *lctx);
+
+/* Saving */
+
+struct sieve_storage_save_context *
+sieve_file_storage_save_alloc(struct sieve_storage *storage);
+int sieve_file_storage_save_init(struct sieve_storage_save_context *sctx,
+ const char *scriptname, struct istream *input);
+int sieve_file_storage_save_continue
+ (struct sieve_storage_save_context *sctx);
+int sieve_file_storage_save_finish
+ (struct sieve_storage_save_context *sctx);
+struct sieve_script *sieve_file_storage_save_get_tempscript
+ (struct sieve_storage_save_context *sctx);
+int sieve_file_storage_save_commit
+ (struct sieve_storage_save_context *sctx);
+void sieve_file_storage_save_cancel
+ (struct sieve_storage_save_context *sctx);
+
+int sieve_file_storage_save_as
+ (struct sieve_storage *storage, struct istream *input,
+ const char *name);
+int sieve_file_storage_save_as_active
+ (struct sieve_storage *storage, struct istream *input,
+ time_t mtime);
+
+/* Quota */
+
+int sieve_file_storage_quota_havespace
+(struct sieve_storage *storage, const char *scriptname, size_t size,
+ enum sieve_storage_quota *quota_r, uint64_t *limit_r);
+
+/*
+ * Sieve script filenames
+ */
+
+const char *sieve_script_file_get_scriptname(const char *filename);
+const char *sieve_script_file_from_name(const char *name);
+
+/*
+ * Script class
+ */
+
+struct sieve_file_script {
+ struct sieve_script script;
+
+ struct stat st;
+ struct stat lnk_st;
+
+ const char *path;
+ const char *dirpath;
+ const char *filename;
+ const char *binpath;
+ const char *binprefix;
+
+ time_t prev_mtime;
+};
+
+struct sieve_file_script *sieve_file_script_init_from_filename
+ (struct sieve_file_storage *fstorage, const char *filename,
+ const char *scriptname);
+struct sieve_file_script *sieve_file_script_open_from_filename
+ (struct sieve_file_storage *fstorage, const char *filename,
+ const char *scriptname);
+struct sieve_file_script *sieve_file_script_init_from_name
+ (struct sieve_file_storage *fstorage, const char *name);
+struct sieve_file_script *sieve_file_script_open_from_name
+ (struct sieve_file_storage *fstorage, const char *name);
+
+struct sieve_file_script *sieve_file_script_init_from_path
+ (struct sieve_file_storage *fstorage, const char *path,
+ const char *scriptname, enum sieve_error *error_r)
+ ATTR_NULL(4);
+struct sieve_file_script *sieve_file_script_open_from_path
+ (struct sieve_file_storage *fstorage, const char *path,
+ const char *scriptname, enum sieve_error *error_r)
+ ATTR_NULL(4);
+
+/* Return directory where script resides in. Returns NULL if this is not a file
+ * script.
+ */
+const char *sieve_file_script_get_dirpath
+ (const struct sieve_script *script);
+
+/* Return full path to file script. Returns NULL if this is not a file script.
+ */
+const char *sieve_file_script_get_path
+ (const struct sieve_script *script);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_file_storage_get_script_sequence
+ (struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_file_script_sequence_next
+ (struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_file_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/Makefile.am b/pigeonhole/src/lib-sieve/storage/ldap/Makefile.am
new file mode 100644
index 0000000..86df92c
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/Makefile.am
@@ -0,0 +1,32 @@
+noinst_LTLIBRARIES = libsieve_storage_ldap.la
+
+sieve_plugindir = $(dovecot_moduledir)/sieve
+sieve_plugin_LTLIBRARIES =
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+ldap_sources = \
+ sieve-ldap-db.c \
+ sieve-ldap-script.c \
+ sieve-ldap-storage.c \
+ sieve-ldap-storage-settings.c
+
+libsieve_storage_ldap_la_SOURCES = $(ldap_sources)
+libsieve_storage_ldap_la_LIBADD = $(LDAP_LIBS)
+
+noinst_HEADERS = \
+ sieve-ldap-db.h \
+ sieve-ldap-storage.h
+
+if LDAP_PLUGIN
+sieve_plugin_LTLIBRARIES += lib10_sieve_storage_ldap_plugin.la
+
+lib10_sieve_storage_ldap_plugin_la_LDFLAGS = -module -avoid-version
+lib10_sieve_storage_ldap_plugin_la_LIBADD = $(LDAP_LIBS)
+lib10_sieve_storage_ldap_plugin_la_DEPENDENCIES = $(LDAP_LIBS)
+lib10_sieve_storage_ldap_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+lib10_sieve_storage_ldap_plugin_la_SOURCES = $(ldap_sources)
+endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in b/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in
new file mode 100644
index 0000000..232f781
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in
@@ -0,0 +1,843 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@LDAP_PLUGIN_TRUE@am__append_1 = lib10_sieve_storage_ldap_plugin.la
+subdir = src/lib-sieve/storage/ldap
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(sieve_plugindir)"
+LTLIBRARIES = $(noinst_LTLIBRARIES) $(sieve_plugin_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+am__lib10_sieve_storage_ldap_plugin_la_SOURCES_DIST = sieve-ldap-db.c \
+ sieve-ldap-script.c sieve-ldap-storage.c \
+ sieve-ldap-storage-settings.c
+am__objects_1 = lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo \
+ lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo \
+ lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo \
+ lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo
+@LDAP_PLUGIN_TRUE@am_lib10_sieve_storage_ldap_plugin_la_OBJECTS = \
+@LDAP_PLUGIN_TRUE@ $(am__objects_1)
+lib10_sieve_storage_ldap_plugin_la_OBJECTS = \
+ $(am_lib10_sieve_storage_ldap_plugin_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lib10_sieve_storage_ldap_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) \
+ --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \
+ $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lib10_sieve_storage_ldap_plugin_la_LDFLAGS) $(LDFLAGS) -o $@
+@LDAP_PLUGIN_TRUE@am_lib10_sieve_storage_ldap_plugin_la_rpath = \
+@LDAP_PLUGIN_TRUE@ -rpath $(sieve_plugindir)
+libsieve_storage_ldap_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__objects_2 = sieve-ldap-db.lo sieve-ldap-script.lo \
+ sieve-ldap-storage.lo sieve-ldap-storage-settings.lo
+am_libsieve_storage_ldap_la_OBJECTS = $(am__objects_2)
+libsieve_storage_ldap_la_OBJECTS = \
+ $(am_libsieve_storage_ldap_la_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Plo \
+ ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Plo \
+ ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Plo \
+ ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Plo \
+ ./$(DEPDIR)/sieve-ldap-db.Plo \
+ ./$(DEPDIR)/sieve-ldap-script.Plo \
+ ./$(DEPDIR)/sieve-ldap-storage-settings.Plo \
+ ./$(DEPDIR)/sieve-ldap-storage.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lib10_sieve_storage_ldap_plugin_la_SOURCES) \
+ $(libsieve_storage_ldap_la_SOURCES)
+DIST_SOURCES = $(am__lib10_sieve_storage_ldap_plugin_la_SOURCES_DIST) \
+ $(libsieve_storage_ldap_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_storage_ldap.la
+sieve_plugindir = $(dovecot_moduledir)/sieve
+sieve_plugin_LTLIBRARIES = $(am__append_1)
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src/lib-sieve
+
+ldap_sources = \
+ sieve-ldap-db.c \
+ sieve-ldap-script.c \
+ sieve-ldap-storage.c \
+ sieve-ldap-storage-settings.c
+
+libsieve_storage_ldap_la_SOURCES = $(ldap_sources)
+libsieve_storage_ldap_la_LIBADD = $(LDAP_LIBS)
+noinst_HEADERS = \
+ sieve-ldap-db.h \
+ sieve-ldap-storage.h
+
+@LDAP_PLUGIN_TRUE@lib10_sieve_storage_ldap_plugin_la_LDFLAGS = -module -avoid-version
+@LDAP_PLUGIN_TRUE@lib10_sieve_storage_ldap_plugin_la_LIBADD = $(LDAP_LIBS)
+@LDAP_PLUGIN_TRUE@lib10_sieve_storage_ldap_plugin_la_DEPENDENCIES = $(LDAP_LIBS)
+@LDAP_PLUGIN_TRUE@lib10_sieve_storage_ldap_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -DPLUGIN_BUILD
+@LDAP_PLUGIN_TRUE@lib10_sieve_storage_ldap_plugin_la_SOURCES = $(ldap_sources)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/storage/ldap/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/storage/ldap/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-sieve_pluginLTLIBRARIES: $(sieve_plugin_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(sieve_plugin_LTLIBRARIES)'; test -n "$(sieve_plugindir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sieve_plugindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sieve_plugindir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(sieve_plugindir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(sieve_plugindir)"; \
+ }
+
+uninstall-sieve_pluginLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sieve_plugin_LTLIBRARIES)'; test -n "$(sieve_plugindir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(sieve_plugindir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(sieve_plugindir)/$$f"; \
+ done
+
+clean-sieve_pluginLTLIBRARIES:
+ -test -z "$(sieve_plugin_LTLIBRARIES)" || rm -f $(sieve_plugin_LTLIBRARIES)
+ @list='$(sieve_plugin_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+lib10_sieve_storage_ldap_plugin.la: $(lib10_sieve_storage_ldap_plugin_la_OBJECTS) $(lib10_sieve_storage_ldap_plugin_la_DEPENDENCIES) $(EXTRA_lib10_sieve_storage_ldap_plugin_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lib10_sieve_storage_ldap_plugin_la_LINK) $(am_lib10_sieve_storage_ldap_plugin_la_rpath) $(lib10_sieve_storage_ldap_plugin_la_OBJECTS) $(lib10_sieve_storage_ldap_plugin_la_LIBADD) $(LIBS)
+
+libsieve_storage_ldap.la: $(libsieve_storage_ldap_la_OBJECTS) $(libsieve_storage_ldap_la_DEPENDENCIES) $(EXTRA_libsieve_storage_ldap_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_storage_ldap_la_OBJECTS) $(libsieve_storage_ldap_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-ldap-db.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-ldap-script.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-ldap-storage-settings.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sieve-ldap-storage.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo: sieve-ldap-db.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo -MD -MP -MF $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Tpo -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo `test -f 'sieve-ldap-db.c' || echo '$(srcdir)/'`sieve-ldap-db.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Tpo $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sieve-ldap-db.c' object='lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.lo `test -f 'sieve-ldap-db.c' || echo '$(srcdir)/'`sieve-ldap-db.c
+
+lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo: sieve-ldap-script.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo -MD -MP -MF $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Tpo -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo `test -f 'sieve-ldap-script.c' || echo '$(srcdir)/'`sieve-ldap-script.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Tpo $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sieve-ldap-script.c' object='lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.lo `test -f 'sieve-ldap-script.c' || echo '$(srcdir)/'`sieve-ldap-script.c
+
+lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo: sieve-ldap-storage.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo -MD -MP -MF $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Tpo -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo `test -f 'sieve-ldap-storage.c' || echo '$(srcdir)/'`sieve-ldap-storage.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Tpo $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sieve-ldap-storage.c' object='lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.lo `test -f 'sieve-ldap-storage.c' || echo '$(srcdir)/'`sieve-ldap-storage.c
+
+lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo: sieve-ldap-storage-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo -MD -MP -MF $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Tpo -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo `test -f 'sieve-ldap-storage-settings.c' || echo '$(srcdir)/'`sieve-ldap-storage-settings.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Tpo $(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sieve-ldap-storage-settings.c' object='lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lib10_sieve_storage_ldap_plugin_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.lo `test -f 'sieve-ldap-storage-settings.c' || echo '$(srcdir)/'`sieve-ldap-storage-settings.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(sieve_plugindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sieve_pluginLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-db.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-storage-settings.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-storage.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-sieve_pluginLTLIBRARIES
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-db.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-script.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage-settings.Plo
+ -rm -f ./$(DEPDIR)/lib10_sieve_storage_ldap_plugin_la-sieve-ldap-storage.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-db.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-script.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-storage-settings.Plo
+ -rm -f ./$(DEPDIR)/sieve-ldap-storage.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-sieve_pluginLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sieve_pluginLTLIBRARIES cscopelist-am ctags ctags-am \
+ distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-sieve_pluginLTLIBRARIES \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-sieve_pluginLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.c b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.c
new file mode 100644
index 0000000..0a7b440
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.c
@@ -0,0 +1,1378 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+/* FIXME: Imported this from Dovecot auth for now. We're working on a proper
+ lib-ldap, but, until then, some code is duplicated here. */
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "net.h"
+#include "ioloop.h"
+#include "array.h"
+#include "hash.h"
+#include "aqueue.h"
+#include "str.h"
+#include "time-util.h"
+#include "env-util.h"
+#include "var-expand.h"
+#include "istream.h"
+
+#include <stddef.h>
+#include <unistd.h>
+
+struct db_ldap_result {
+ int refcount;
+ LDAPMessage *msg;
+};
+
+struct db_ldap_result_iterate_context {
+ pool_t pool;
+
+ struct auth_request *auth_request;
+ const ARRAY_TYPE(ldap_field) *attr_map;
+ unsigned int attr_idx;
+
+ /* attribute name => value */
+ HASH_TABLE(char *, struct db_ldap_value *) ldap_attrs;
+
+ const char *val_1_arr[2];
+ string_t *var, *debug;
+
+ bool skip_null_values;
+ bool iter_dn_values;
+};
+
+struct db_ldap_sasl_bind_context {
+ const char *authcid;
+ const char *passwd;
+ const char *realm;
+ const char *authzid;
+};
+
+static struct ldap_connection *ldap_connections = NULL;
+
+static int db_ldap_bind(struct ldap_connection *conn);
+static void db_ldap_conn_close(struct ldap_connection *conn);
+
+int ldap_deref_from_str(const char *str, int *deref_r)
+{
+ if (strcasecmp(str, "never") == 0)
+ *deref_r = LDAP_DEREF_NEVER;
+ else if (strcasecmp(str, "searching") == 0)
+ *deref_r = LDAP_DEREF_SEARCHING;
+ else if (strcasecmp(str, "finding") == 0)
+ *deref_r = LDAP_DEREF_FINDING;
+ else if (strcasecmp(str, "always") == 0)
+ *deref_r = LDAP_DEREF_ALWAYS;
+ else
+ return -1;
+ return 0;
+}
+
+int ldap_scope_from_str(const char *str, int *scope_r)
+{
+ if (strcasecmp(str, "base") == 0)
+ *scope_r = LDAP_SCOPE_BASE;
+ else if (strcasecmp(str, "onelevel") == 0)
+ *scope_r = LDAP_SCOPE_ONELEVEL;
+ else if (strcasecmp(str, "subtree") == 0)
+ *scope_r = LDAP_SCOPE_SUBTREE;
+ else
+ return -1;
+ return 0;
+}
+
+#ifdef OPENLDAP_TLS_OPTIONS
+int ldap_tls_require_cert_from_str(const char *str, int *opt_x_tls_r)
+{
+ if (strcasecmp(str, "never") == 0)
+ *opt_x_tls_r = LDAP_OPT_X_TLS_NEVER;
+ else if (strcasecmp(str, "hard") == 0)
+ *opt_x_tls_r = LDAP_OPT_X_TLS_HARD;
+ else if (strcasecmp(str, "demand") == 0)
+ *opt_x_tls_r = LDAP_OPT_X_TLS_DEMAND;
+ else if (strcasecmp(str, "allow") == 0)
+ *opt_x_tls_r = LDAP_OPT_X_TLS_ALLOW;
+ else if (strcasecmp(str, "try") == 0)
+ *opt_x_tls_r = LDAP_OPT_X_TLS_TRY;
+ else
+ return -1;
+ return 0;
+}
+#endif
+
+
+static int ldap_get_errno(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ int ret, err;
+
+ ret = ldap_get_option(conn->ld, LDAP_OPT_ERROR_NUMBER, (void *) &err);
+ if (ret != LDAP_SUCCESS) {
+ e_error(storage->event, "db: "
+ "Can't get error number: %s",
+ ldap_err2string(ret));
+ return LDAP_UNAVAILABLE;
+ }
+
+ return err;
+}
+
+const char *ldap_get_error(struct ldap_connection *conn)
+{
+ const char *ret;
+ char *str = NULL;
+
+ ret = ldap_err2string(ldap_get_errno(conn));
+
+ ldap_get_option(conn->ld, LDAP_OPT_ERROR_STRING, (void *)&str);
+ if (str != NULL) {
+ ret = t_strconcat(ret, ", ", str, NULL);
+ ldap_memfree(str);
+ }
+ ldap_set_option(conn->ld, LDAP_OPT_ERROR_STRING, NULL);
+ return ret;
+}
+
+static void ldap_conn_reconnect(struct ldap_connection *conn)
+{
+ db_ldap_conn_close(conn);
+ if (sieve_ldap_db_connect(conn) < 0)
+ db_ldap_conn_close(conn);
+}
+
+static int ldap_handle_error(struct ldap_connection *conn)
+{
+ int err = ldap_get_errno(conn);
+
+ switch (err) {
+ case LDAP_SUCCESS:
+ i_unreached();
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ case LDAP_UNDEFINED_TYPE:
+ case LDAP_INAPPROPRIATE_MATCHING:
+ case LDAP_CONSTRAINT_VIOLATION:
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ case LDAP_INVALID_SYNTAX:
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_ALIAS_PROBLEM:
+ case LDAP_INVALID_DN_SYNTAX:
+ case LDAP_IS_LEAF:
+ case LDAP_ALIAS_DEREF_PROBLEM:
+ case LDAP_FILTER_ERROR:
+ /* invalid input */
+ return -1;
+ case LDAP_SERVER_DOWN:
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+ case LDAP_BUSY:
+#ifdef LDAP_CONNECT_ERROR
+ case LDAP_CONNECT_ERROR:
+#endif
+ case LDAP_LOCAL_ERROR:
+ case LDAP_INVALID_CREDENTIALS:
+ case LDAP_OPERATIONS_ERROR:
+ default:
+ /* connection problems */
+ ldap_conn_reconnect(conn);
+ return 0;
+ }
+}
+
+static int db_ldap_request_search(struct ldap_connection *conn,
+ struct ldap_request *request)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND);
+ i_assert(request->msgid == -1);
+
+ request->msgid =
+ ldap_search(conn->ld, *request->base == '\0' ? NULL :
+ request->base, request->scope,
+ request->filter, request->attributes, 0);
+ if (request->msgid == -1) {
+ e_error(storage->event, "db: "
+ "ldap_search(%s) parsing failed: %s",
+ request->filter, ldap_get_error(conn));
+ if (ldap_handle_error(conn) < 0) {
+ /* broken request, remove it */
+ return 0;
+ }
+ return -1;
+ }
+ return 1;
+}
+
+static bool db_ldap_request_queue_next(struct ldap_connection *conn)
+{
+ struct ldap_request *const *requestp, *request;
+ int ret = -1;
+
+ /* connecting may call db_ldap_connect_finish(), which gets us back
+ here. so do the connection before checking the request queue. */
+ if (sieve_ldap_db_connect(conn) < 0)
+ return FALSE;
+
+ if (conn->pending_count == aqueue_count(conn->request_queue)) {
+ /* no non-pending requests */
+ return FALSE;
+ }
+ if (conn->pending_count > DB_LDAP_MAX_PENDING_REQUESTS) {
+ /* wait until server has replied to some requests */
+ return FALSE;
+ }
+
+ requestp = array_idx(&conn->request_array,
+ aqueue_idx(conn->request_queue,
+ conn->pending_count));
+ request = *requestp;
+
+ switch (conn->conn_state) {
+ case LDAP_CONN_STATE_DISCONNECTED:
+ case LDAP_CONN_STATE_BINDING:
+ /* wait until we're in bound state */
+ return FALSE;
+ case LDAP_CONN_STATE_BOUND:
+ /* we can do anything in this state */
+ break;
+ }
+
+ ret = db_ldap_request_search(conn, request);
+ if (ret > 0) {
+ /* success */
+ i_assert(request->msgid != -1);
+ conn->pending_count++;
+ return TRUE;
+ } else if (ret < 0) {
+ /* disconnected */
+ return FALSE;
+ } else {
+ /* broken request, remove from queue */
+ aqueue_delete_tail(conn->request_queue);
+ request->callback(conn, request, NULL);
+ return TRUE;
+ }
+}
+
+static bool
+db_ldap_check_limits(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct ldap_request *const *first_requestp;
+ unsigned int count;
+ time_t secs_diff;
+
+ count = aqueue_count(conn->request_queue);
+ if (count == 0)
+ return TRUE;
+
+ first_requestp = array_idx(&conn->request_array,
+ aqueue_idx(conn->request_queue, 0));
+ secs_diff = ioloop_time - (*first_requestp)->create_time;
+ if (secs_diff > DB_LDAP_REQUEST_LOST_TIMEOUT_SECS) {
+ e_error(storage->event, "db: "
+ "Connection appears to be hanging, reconnecting");
+ ldap_conn_reconnect(conn);
+ return TRUE;
+ }
+ return TRUE;
+}
+
+void db_ldap_request(struct ldap_connection *conn,
+ struct ldap_request *request)
+{
+ request->msgid = -1;
+ request->create_time = ioloop_time;
+
+ if (!db_ldap_check_limits(conn)) {
+ request->callback(conn, request, NULL);
+ return;
+ }
+
+ aqueue_append(conn->request_queue, &request);
+ (void)db_ldap_request_queue_next(conn);
+}
+
+static int db_ldap_connect_finish(struct ldap_connection *conn, int ret)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+
+ if (ret == LDAP_SERVER_DOWN) {
+ e_error(storage->event, "db: "
+ "Can't connect to server: %s",
+ set->uris != NULL ?
+ set->uris : set->hosts);
+ return -1;
+ }
+ if (ret != LDAP_SUCCESS) {
+ e_error(storage->event, "db: "
+ "binding failed (dn %s): %s",
+ set->dn == NULL ? "(none)" : set->dn,
+ ldap_get_error(conn));
+ return -1;
+ }
+
+ timeout_remove(&conn->to);
+ conn->conn_state = LDAP_CONN_STATE_BOUND;
+ e_debug(storage->event, "db: "
+ "Successfully bound (dn %s)",
+ set->dn == NULL ? "(none)" : set->dn);
+ while (db_ldap_request_queue_next(conn))
+ ;
+ return 0;
+}
+
+static void db_ldap_default_bind_finished(struct ldap_connection *conn,
+ struct db_ldap_result *res)
+{
+ int ret;
+
+ i_assert(conn->pending_count == 0);
+ conn->default_bind_msgid = -1;
+
+ ret = ldap_result2error(conn->ld, res->msg, FALSE);
+ if (db_ldap_connect_finish(conn, ret) < 0) {
+ /* lost connection, close it */
+ db_ldap_conn_close(conn);
+ }
+}
+
+static void db_ldap_abort_requests(struct ldap_connection *conn,
+ unsigned int max_count,
+ unsigned int timeout_secs,
+ bool error, const char *reason)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct ldap_request *const *requestp, *request;
+ time_t diff;
+
+ while (aqueue_count(conn->request_queue) > 0 && max_count > 0) {
+ requestp = array_idx(&conn->request_array,
+ aqueue_idx(conn->request_queue, 0));
+ request = *requestp;
+
+ diff = ioloop_time - request->create_time;
+ if (diff < (time_t)timeout_secs)
+ break;
+
+ /* timed out, abort */
+ aqueue_delete_tail(conn->request_queue);
+
+ if (request->msgid != -1) {
+ i_assert(conn->pending_count > 0);
+ conn->pending_count--;
+ }
+ if (error)
+ e_error(storage->event, "db: %s", reason);
+ else
+ e_debug(storage->event, "db: %s", reason);
+ request->callback(conn, request, NULL);
+ max_count--;
+ }
+}
+
+static struct ldap_request *
+db_ldap_find_request(struct ldap_connection *conn, int msgid,
+ unsigned int *idx_r)
+{
+ struct ldap_request *const *requests, *request = NULL;
+ unsigned int i, count;
+
+ count = aqueue_count(conn->request_queue);
+ if (count == 0)
+ return NULL;
+
+ requests = array_idx(&conn->request_array, 0);
+ for (i = 0; i < count; i++) {
+ request = requests[aqueue_idx(conn->request_queue, i)];
+ if (request->msgid == msgid) {
+ *idx_r = i;
+ return request;
+ }
+ if (request->msgid == -1)
+ break;
+ }
+ return NULL;
+}
+
+static bool
+db_ldap_handle_request_result(struct ldap_connection *conn,
+ struct ldap_request *request, unsigned int idx,
+ struct db_ldap_result *res)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ int ret;
+ bool final_result;
+
+ i_assert(conn->pending_count > 0);
+
+ switch (ldap_msgtype(res->msg)) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_RESULT:
+ break;
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* we're going to ignore this */
+ return FALSE;
+ default:
+ e_error(storage->event, "db: Reply with unexpected type %d",
+ ldap_msgtype(res->msg));
+ return TRUE;
+ }
+
+ if (ldap_msgtype(res->msg) == LDAP_RES_SEARCH_ENTRY) {
+ ret = LDAP_SUCCESS;
+ final_result = FALSE;
+ } else {
+ final_result = TRUE;
+ ret = ldap_result2error(conn->ld, res->msg, 0);
+ }
+ if (ret != LDAP_SUCCESS) {
+ /* handle search failures here */
+ e_error(storage->event, "db: "
+ "ldap_search(base=%s filter=%s) failed: %s",
+ request->base, request->filter,
+ ldap_err2string(ret));
+ res = NULL;
+ } else {
+ if (!final_result && storage->svinst->debug) {
+ e_debug(storage->event,
+ "db: ldap_search(base=%s filter=%s) returned entry: %s",
+ request->base, request->filter,
+ ldap_get_dn(conn->ld, res->msg));
+ }
+ }
+ if (res == NULL && !final_result) {
+ /* wait for the final reply */
+ request->failed = TRUE;
+ return TRUE;
+ }
+ if (request->failed)
+ res = NULL;
+ if (final_result) {
+ conn->pending_count--;
+ aqueue_delete(conn->request_queue, idx);
+ }
+
+ T_BEGIN {
+ request->callback(conn, request, res == NULL ? NULL : res->msg);
+ } T_END;
+
+ if (idx > 0) {
+ /* see if there are timed out requests */
+ db_ldap_abort_requests(conn, idx,
+ DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
+ TRUE, "Request lost");
+ }
+ return TRUE;
+}
+
+static void db_ldap_result_unref(struct db_ldap_result **_res)
+{
+ struct db_ldap_result *res = *_res;
+
+ *_res = NULL;
+ i_assert(res->refcount > 0);
+ if (--res->refcount == 0) {
+ ldap_msgfree(res->msg);
+ i_free(res);
+ }
+}
+
+static void
+db_ldap_request_free(struct ldap_request *request)
+{
+ if (request->result != NULL)
+ db_ldap_result_unref(&request->result);
+}
+
+static void
+db_ldap_handle_result(struct ldap_connection *conn, struct db_ldap_result *res)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct ldap_request *request;
+ unsigned int idx;
+ int msgid;
+
+ msgid = ldap_msgid(res->msg);
+ if (msgid == conn->default_bind_msgid) {
+ db_ldap_default_bind_finished(conn, res);
+ return;
+ }
+
+ request = db_ldap_find_request(conn, msgid, &idx);
+ if (request == NULL) {
+ e_error(storage->event,
+ "db: Reply with unknown msgid %d", msgid);
+ return;
+ }
+
+ if (db_ldap_handle_request_result(conn, request, idx, res))
+ db_ldap_request_free(request);
+}
+
+static void ldap_input(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct timeval timeout;
+ struct db_ldap_result *res;
+ LDAPMessage *msg;
+ time_t prev_reply_diff;
+ int ret;
+
+ do {
+ if (conn->ld == NULL)
+ return;
+
+ i_zero(&timeout);
+ ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &msg);
+#ifdef OPENLDAP_ASYNC_WORKAROUND
+ if (ret == 0) {
+ /* try again, there may be another in buffer */
+ ret = ldap_result(conn->ld, LDAP_RES_ANY, 0,
+ &timeout, &msg);
+ }
+#endif
+ if (ret <= 0)
+ break;
+
+ res = i_new(struct db_ldap_result, 1);
+ res->refcount = 1;
+ res->msg = msg;
+ db_ldap_handle_result(conn, res);
+ db_ldap_result_unref(&res);
+ } while (conn->io != NULL);
+
+ prev_reply_diff = ioloop_time - conn->last_reply_stamp;
+ conn->last_reply_stamp = ioloop_time;
+
+ if (ret > 0) {
+ /* input disabled, continue once it's enabled */
+ i_assert(conn->io == NULL);
+ } else if (ret == 0) {
+ /* send more requests */
+ while (db_ldap_request_queue_next(conn))
+ ;
+ } else if (ldap_get_errno(conn) != LDAP_SERVER_DOWN) {
+ e_error(storage->event, "db: ldap_result() failed: %s",
+ ldap_get_error(conn));
+ ldap_conn_reconnect(conn);
+ } else if (aqueue_count(conn->request_queue) > 0 ||
+ prev_reply_diff < DB_LDAP_IDLE_RECONNECT_SECS) {
+ e_error(storage->event,
+ "db: Connection lost to LDAP server, reconnecting");
+ ldap_conn_reconnect(conn);
+ } else {
+ /* server probably disconnected an idle connection. don't
+ reconnect until the next request comes. */
+ db_ldap_conn_close(conn);
+ }
+}
+
+#ifdef HAVE_LDAP_SASL
+static int
+sasl_interact(LDAP *ld ATTR_UNUSED, unsigned flags ATTR_UNUSED,
+ void *defaults, void *interact)
+{
+ struct db_ldap_sasl_bind_context *context = defaults;
+ sasl_interact_t *in;
+ const char *str;
+
+ for (in = interact; in->id != SASL_CB_LIST_END; in++) {
+ switch (in->id) {
+ case SASL_CB_GETREALM:
+ str = context->realm;
+ break;
+ case SASL_CB_AUTHNAME:
+ str = context->authcid;
+ break;
+ case SASL_CB_USER:
+ str = context->authzid;
+ break;
+ case SASL_CB_PASS:
+ str = context->passwd;
+ break;
+ default:
+ str = NULL;
+ break;
+ }
+ if (str != NULL) {
+ in->len = strlen(str);
+ in->result = str;
+ }
+
+ }
+ return LDAP_SUCCESS;
+}
+#endif
+
+static void ldap_connection_timeout(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
+
+ e_error(storage->event, "db: Initial binding to LDAP server timed out");
+ db_ldap_conn_close(conn);
+}
+
+static int db_ldap_bind(struct ldap_connection *conn)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+ int msgid;
+
+ i_assert(conn->conn_state != LDAP_CONN_STATE_BINDING);
+ i_assert(conn->default_bind_msgid == -1);
+ i_assert(conn->pending_count == 0);
+
+ msgid = ldap_bind(conn->ld, set->dn, set->dnpass,
+ LDAP_AUTH_SIMPLE);
+ if (msgid == -1) {
+ i_assert(ldap_get_errno(conn) != LDAP_SUCCESS);
+ if (db_ldap_connect_finish(conn, ldap_get_errno(conn)) < 0) {
+ /* lost connection, close it */
+ db_ldap_conn_close(conn);
+ }
+ return -1;
+ }
+
+ conn->conn_state = LDAP_CONN_STATE_BINDING;
+ conn->default_bind_msgid = msgid;
+
+ timeout_remove(&conn->to);
+ conn->to = timeout_add(DB_LDAP_REQUEST_LOST_TIMEOUT_SECS*1000,
+ ldap_connection_timeout, conn);
+ return 0;
+}
+
+static int db_ldap_get_fd(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ int ret;
+
+ /* get the connection's fd */
+ ret = ldap_get_option(conn->ld, LDAP_OPT_DESC, (void *)&conn->fd);
+ if (ret != LDAP_SUCCESS) {
+ e_error(storage->event, "db: Can't get connection fd: %s",
+ ldap_err2string(ret));
+ return -1;
+ }
+ if (conn->fd <= STDERR_FILENO) {
+ /* Solaris LDAP library seems to be broken */
+ e_error(storage->event,
+ "db: Buggy LDAP library returned wrong fd: %d",
+ conn->fd);
+ return -1;
+ }
+ i_assert(conn->fd != -1);
+ net_set_nonblock(conn->fd, TRUE);
+ return 0;
+}
+
+static int
+db_ldap_set_opt(struct ldap_connection *conn, int opt, const void *value,
+ const char *optname, const char *value_str)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ int ret;
+
+ ret = ldap_set_option(conn->ld, opt, value);
+ if (ret != LDAP_SUCCESS) {
+ e_error(storage->event, "db: Can't set option %s to %s: %s",
+ optname, value_str, ldap_err2string(ret));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+db_ldap_set_opt_str(struct ldap_connection *conn, int opt, const char *value,
+ const char *optname)
+{
+ if (value != NULL)
+ return db_ldap_set_opt(conn, opt, value, optname, value);
+ return 0;
+}
+
+static int db_ldap_set_tls_options(struct ldap_connection *conn)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+
+ if (!set->tls)
+ return 0;
+
+#ifdef OPENLDAP_TLS_OPTIONS
+ if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CACERTFILE,
+ set->tls_ca_cert_file, "tls_ca_cert_file") < 0)
+ return -1;
+ if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CACERTDIR,
+ set->tls_ca_cert_dir, "tls_ca_cert_dir") < 0)
+ return -1;
+ if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CERTFILE,
+ set->tls_cert_file, "tls_cert_file") < 0)
+ return -1;
+ if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_KEYFILE,
+ set->tls_key_file, "tls_key_file") < 0)
+ return -1;
+ if (db_ldap_set_opt_str(conn, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ set->tls_cipher_suite, "tls_cipher_suite") < 0)
+ return -1;
+ if (set->tls_require_cert != NULL) {
+ if (db_ldap_set_opt(conn, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &set->ldap_tls_require_cert,
+ "tls_require_cert", set->tls_require_cert) < 0)
+ return -1;
+ }
+#else
+ if (set->tls_ca_cert_file != NULL ||
+ set->tls_ca_cert_dir != NULL ||
+ set->tls_cert_file != NULL ||
+ set->tls_key_file != NULL ||
+ set->tls_cipher_suite != NULL) {
+ e_warning(&conn->lstorage->storage, "db: "
+ "tls_* settings ignored, "
+ "your LDAP library doesn't seem to support them");
+ }
+#endif
+ return 0;
+}
+
+static int db_ldap_set_options(struct ldap_connection *conn)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ unsigned int ldap_version;
+ int value;
+
+ if (db_ldap_set_opt(conn, LDAP_OPT_DEREF, &set->ldap_deref,
+ "deref", set->deref) < 0)
+ return -1;
+#ifdef LDAP_OPT_DEBUG_LEVEL
+ if (str_to_int(set->debug_level, &value) >= 0 && value != 0) {
+ if (db_ldap_set_opt(conn, LDAP_OPT_DEBUG_LEVEL, &value,
+ "debug_level", set->debug_level) < 0)
+ return -1;
+ }
+#endif
+
+ if (set->ldap_version < 3) {
+ if (set->sasl_bind) {
+ e_error(storage->event,
+ "db: sasl_bind=yes requires ldap_version=3");
+ return -1;
+ }
+ if (set->tls) {
+ e_error(storage->event,
+ "db: tls=yes requires ldap_version=3");
+ return -1;
+ }
+ }
+
+ ldap_version = set->ldap_version;
+ if (db_ldap_set_opt(conn, LDAP_OPT_PROTOCOL_VERSION, &ldap_version,
+ "protocol_version", dec2str(ldap_version)) < 0)
+ return -1;
+ if (db_ldap_set_tls_options(conn) < 0)
+ return -1;
+ return 0;
+}
+
+int sieve_ldap_db_connect(struct ldap_connection *conn)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct timeval start, end;
+ int debug_level;
+ bool debug;
+#if defined(HAVE_LDAP_SASL) || defined(LDAP_HAVE_START_TLS_S)
+ int ret;
+#endif
+
+ if (conn->conn_state != LDAP_CONN_STATE_DISCONNECTED)
+ return 0;
+
+ debug = FALSE;
+ if (str_to_int(set->debug_level, &debug_level) >= 0)
+ debug = debug_level > 0;
+
+ if (debug)
+ i_gettimeofday(&start);
+ i_assert(conn->pending_count == 0);
+ if (conn->ld == NULL) {
+ if (set->uris != NULL) {
+#ifdef LDAP_HAVE_INITIALIZE
+ if (ldap_initialize(&conn->ld, set->uris) != LDAP_SUCCESS)
+ conn->ld = NULL;
+#else
+ e_error(storage->event, "db: "
+ "Your LDAP library doesn't support "
+ "'uris' setting, use 'hosts' instead.");
+ return -1;
+#endif
+ } else
+ conn->ld = ldap_init(set->hosts, LDAP_PORT);
+
+ if (conn->ld == NULL) {
+ e_error(storage->event, "db: "
+ "ldap_init() failed with hosts: %s", set->hosts);
+ return -1;
+ }
+
+ if (db_ldap_set_options(conn) < 0)
+ return -1;
+ }
+
+ if (set->tls) {
+#ifdef LDAP_HAVE_START_TLS_S
+ ret = ldap_start_tls_s(conn->ld, NULL, NULL);
+ if (ret != LDAP_SUCCESS) {
+ if (ret == LDAP_OPERATIONS_ERROR &&
+ set->uris != NULL &&
+ str_begins(set->uris, "ldaps:")) {
+ e_error(storage->event, "db: "
+ "Don't use both tls=yes and ldaps URI");
+ }
+ e_error(storage->event, "db: "
+ "ldap_start_tls_s() failed: %s",
+ ldap_err2string(ret));
+ return -1;
+ }
+#else
+ e_error(storage->event, "db: "
+ "Your LDAP library doesn't support TLS");
+ return -1;
+#endif
+ }
+
+ if (set->sasl_bind) {
+#ifdef HAVE_LDAP_SASL
+ struct db_ldap_sasl_bind_context context;
+
+ i_zero(&context);
+ context.authcid = set->dn;
+ context.passwd = set->dnpass;
+ context.realm = set->sasl_realm;
+ context.authzid = set->sasl_authz_id;
+
+ /* There doesn't seem to be a way to do SASL binding
+ asynchronously.. */
+ ret = ldap_sasl_interactive_bind_s(conn->ld, NULL,
+ set->sasl_mech,
+ NULL, NULL, LDAP_SASL_QUIET,
+ sasl_interact, &context);
+ if (db_ldap_connect_finish(conn, ret) < 0)
+ return -1;
+#else
+ e_error(storage->event, "db: "
+ "sasl_bind=yes but no SASL support compiled in");
+ return -1;
+#endif
+ conn->conn_state = LDAP_CONN_STATE_BOUND;
+ } else {
+ if (db_ldap_bind(conn) < 0)
+ return -1;
+ }
+ if (debug) {
+ i_gettimeofday(&end);
+ int msecs = timeval_diff_msecs(&end, &start);
+ e_debug(storage->event, "db: "
+ "Initialization took %d msecs", msecs);
+ }
+
+ if (db_ldap_get_fd(conn) < 0)
+ return -1;
+ conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+ return 0;
+}
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable)
+{
+ if (!enable) {
+ io_remove(&conn->io);
+ } else {
+ if (conn->io == NULL && conn->fd != -1) {
+ conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+ ldap_input(conn);
+ }
+ }
+}
+
+static void db_ldap_disconnect_timeout(struct ldap_connection *conn)
+{
+ db_ldap_abort_requests(conn, UINT_MAX,
+ DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS, FALSE,
+ "Aborting (timeout), we're not connected to LDAP server");
+
+ if (aqueue_count(conn->request_queue) == 0) {
+ /* no requests left, remove this timeout handler */
+ timeout_remove(&conn->to);
+ }
+}
+
+static void db_ldap_conn_close(struct ldap_connection *conn)
+{
+ struct ldap_request *const *requests, *request;
+ unsigned int i;
+
+ conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+ conn->default_bind_msgid = -1;
+
+ timeout_remove(&conn->to);
+
+ if (conn->pending_count != 0) {
+ requests = array_idx(&conn->request_array, 0);
+ for (i = 0; i < conn->pending_count; i++) {
+ request = requests[aqueue_idx(conn->request_queue, i)];
+
+ i_assert(request->msgid != -1);
+ request->msgid = -1;
+ }
+ conn->pending_count = 0;
+ }
+
+ if (conn->ld != NULL) {
+ ldap_unbind(conn->ld);
+ conn->ld = NULL;
+ }
+ conn->fd = -1;
+
+ /* the fd may have already been closed before ldap_unbind(),
+ so we'll have to use io_remove_closed(). */
+ io_remove_closed(&conn->io);
+
+ if (aqueue_count(conn->request_queue) > 0) {
+ conn->to = timeout_add(DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS *
+ 1000/2, db_ldap_disconnect_timeout, conn);
+ }
+}
+
+struct ldap_field_find_context {
+ ARRAY_TYPE(string) attr_names;
+ pool_t pool;
+};
+
+#define IS_LDAP_ESCAPED_CHAR(c) \
+ ((c) == '*' || (c) == '(' || (c) == ')' || (c) == '\\')
+
+const char *ldap_escape(const char *str)
+{
+ const char *p;
+ string_t *ret;
+
+ for (p = str; *p != '\0'; p++) {
+ if (IS_LDAP_ESCAPED_CHAR(*p))
+ break;
+ }
+
+ if (*p == '\0')
+ return str;
+
+ ret = t_str_new((size_t) (p - str) + 64);
+ str_append_data(ret, str, (size_t) (p - str));
+
+ for (; *p != '\0'; p++) {
+ if (IS_LDAP_ESCAPED_CHAR(*p))
+ str_append_c(ret, '\\');
+ str_append_c(ret, *p);
+ }
+ return str_c(ret);
+}
+
+struct ldap_connection *
+sieve_ldap_db_init(struct sieve_ldap_storage *lstorage)
+{
+ struct ldap_connection *conn;
+ pool_t pool;
+
+ pool = pool_alloconly_create("ldap_connection", 1024);
+ conn = p_new(pool, struct ldap_connection, 1);
+ conn->pool = pool;
+ conn->refcount = 1;
+ conn->lstorage = lstorage;
+
+ conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+ conn->default_bind_msgid = -1;
+ conn->fd = -1;
+
+ i_array_init(&conn->request_array, 512);
+ conn->request_queue = aqueue_init(&conn->request_array.arr);
+
+ conn->next = ldap_connections;
+ ldap_connections = conn;
+ return conn;
+}
+
+void sieve_ldap_db_unref(struct ldap_connection **_conn)
+{
+ struct ldap_connection *conn = *_conn;
+ struct ldap_connection **p;
+
+ *_conn = NULL;
+ i_assert(conn->refcount >= 0);
+ if (--conn->refcount > 0)
+ return;
+
+ for (p = &ldap_connections; *p != NULL; p = &(*p)->next) {
+ if (*p == conn) {
+ *p = conn->next;
+ break;
+ }
+ }
+
+ db_ldap_abort_requests(conn, UINT_MAX, 0, FALSE, "Shutting down");
+ i_assert(conn->pending_count == 0);
+ db_ldap_conn_close(conn);
+ i_assert(conn->to == NULL);
+
+ array_free(&conn->request_array);
+ aqueue_deinit(&conn->request_queue);
+
+ pool_unref(&conn->pool);
+}
+
+static void db_ldap_switch_ioloop(struct ldap_connection *conn)
+{
+ if (conn->to != NULL)
+ conn->to = io_loop_move_timeout(&conn->to);
+ if (conn->io != NULL)
+ conn->io = io_loop_move_io(&conn->io);
+}
+
+static void db_ldap_wait(struct ldap_connection *conn)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct ioloop *prev_ioloop = current_ioloop;
+
+ i_assert(conn->ioloop == NULL);
+
+ if (aqueue_count(conn->request_queue) == 0)
+ return;
+
+ conn->ioloop = io_loop_create();
+ db_ldap_switch_ioloop(conn);
+ /* either we're waiting for network I/O or we're getting out of a
+ callback using timeout_add_short(0) */
+ i_assert(io_loop_have_ios(conn->ioloop) ||
+ io_loop_have_immediate_timeouts(conn->ioloop));
+
+ do {
+ e_debug(storage->event, "db: "
+ "Waiting for %d requests to finish",
+ aqueue_count(conn->request_queue) );
+ io_loop_run(conn->ioloop);
+ } while (aqueue_count(conn->request_queue) > 0);
+
+ e_debug(storage->event, "db: All requests finished");
+
+ current_ioloop = prev_ioloop;
+ db_ldap_switch_ioloop(conn);
+ current_ioloop = conn->ioloop;
+ io_loop_destroy(&conn->ioloop);
+}
+
+static void sieve_ldap_db_script_free(unsigned char *script)
+{
+ i_free(script);
+}
+
+static int
+sieve_ldap_db_get_script_modattr(struct ldap_connection *conn,
+ LDAPMessage *entry, pool_t pool, const char **modattr_r)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ char *attr, **vals;
+ BerElement *ber;
+
+ *modattr_r = NULL;
+
+ attr = ldap_first_attribute(conn->ld, entry, &ber);
+ while (attr != NULL) {
+ if (strcmp(attr, set->sieve_ldap_mod_attr) == 0) {
+ vals = ldap_get_values(conn->ld, entry, attr);
+ if (vals == NULL || vals[0] == NULL)
+ return 0;
+
+ if (vals[1] != NULL) {
+ e_warning(storage->event, "db: "
+ "Search returned more than one Sieve modified attribute `%s'; "
+ "using only the first one.", set->sieve_ldap_mod_attr);
+ }
+
+ *modattr_r = p_strdup(pool, vals[0]);
+
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ return 1;
+ }
+ ldap_memfree(attr);
+ attr = ldap_next_attribute(conn->ld, entry, ber);
+ }
+ ber_free(ber, 0);
+
+ return 0;
+}
+
+static int
+sieve_ldap_db_get_script(struct ldap_connection *conn,
+ LDAPMessage *entry, struct istream **script_r)
+{
+ const struct sieve_ldap_storage_settings *set = &conn->lstorage->set;
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ char *attr;
+ unsigned char *data;
+ size_t size;
+ struct berval **vals;
+ BerElement *ber;
+
+ attr = ldap_first_attribute(conn->ld, entry, &ber);
+ while (attr != NULL) {
+ if (strcmp(attr, set->sieve_ldap_script_attr) == 0) {
+ vals = ldap_get_values_len(conn->ld, entry, attr);
+ if (vals == NULL || vals[0] == NULL)
+ return 0;
+
+ if (vals[1] != NULL) {
+ e_warning(storage->event, "db: "
+ "Search returned more than one Sieve script attribute `%s'; "
+ "using only the first one.", set->sieve_ldap_script_attr);
+ }
+
+ size = vals[0]->bv_len;
+ data = i_malloc(size);
+
+ e_debug(storage->event, "db: "
+ "Found script with length %zu", size);
+
+ memcpy(data, vals[0]->bv_val, size);
+
+ ldap_value_free_len(vals);
+ ldap_memfree(attr);
+
+ *script_r = i_stream_create_from_data(data, size);
+ i_stream_add_destroy_callback
+ (*script_r, sieve_ldap_db_script_free, data);
+ return 1;
+ }
+ ldap_memfree(attr);
+ attr = ldap_next_attribute(conn->ld, entry, ber);
+ }
+ ber_free(ber, 0);
+
+ return 0;
+}
+
+const struct var_expand_table
+auth_request_var_expand_static_tab[] = {
+ { 'u', NULL, "user" },
+ { 'n', NULL, "username" },
+ { 'd', NULL, "domain" },
+ { 'h', NULL, "home" },
+ { '\0', NULL, "name" },
+ { '\0', NULL, NULL }
+};
+
+static const struct var_expand_table *
+db_ldap_get_var_expand_table(struct ldap_connection *conn,
+ const char *name)
+{
+ struct sieve_ldap_storage *lstorage = conn->lstorage;
+ struct sieve_instance *svinst = lstorage->storage.svinst;
+ const unsigned int auth_count =
+ N_ELEMENTS(auth_request_var_expand_static_tab);
+ struct var_expand_table *tab;
+
+ /* keep the extra fields at the beginning. the last static_tab field
+ contains the ending NULL-fields. */
+ tab = t_malloc_no0((auth_count) * sizeof(*tab));
+
+ memcpy(tab, auth_request_var_expand_static_tab,
+ auth_count * sizeof(*tab));
+
+ tab[0].value = ldap_escape(lstorage->username);
+ tab[1].value = ldap_escape(t_strcut(lstorage->username, '@'));
+ tab[2].value = strchr(lstorage->username, '@');
+ if (tab[2].value != NULL)
+ tab[2].value = ldap_escape(tab[2].value+1);
+ tab[3].value = ldap_escape(svinst->home_dir);
+ tab[4].value = ldap_escape(name);
+ return tab;
+}
+
+struct sieve_ldap_script_lookup_request {
+ struct ldap_request request;
+
+ unsigned int entries;
+ const char *result_dn;
+ const char *result_modattr;
+};
+
+static void
+sieve_ldap_lookup_script_callback(struct ldap_connection *conn,
+ struct ldap_request *request, LDAPMessage *res)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct sieve_ldap_script_lookup_request *srequest =
+ (struct sieve_ldap_script_lookup_request *)request;
+
+ if (res == NULL) {
+ io_loop_stop(conn->ioloop);
+ return;
+ }
+
+ if (ldap_msgtype(res) != LDAP_RES_SEARCH_RESULT) {
+ if (srequest->result_dn == NULL) {
+ srequest->result_dn = p_strdup
+ (request->pool, ldap_get_dn(conn->ld, res));
+ (void)sieve_ldap_db_get_script_modattr
+ (conn, res, request->pool, &srequest->result_modattr);
+ } else if (srequest->entries++ == 0) {
+ e_warning(storage->event, "db: "
+ "Search returned more than one entry for Sieve script; "
+ "using only the first one.");
+ }
+ } else {
+ io_loop_stop(conn->ioloop);
+ return;
+ }
+}
+
+int sieve_ldap_db_lookup_script(struct ldap_connection *conn,
+ const char *name, const char **dn_r, const char **modattr_r)
+{
+ struct sieve_ldap_storage *lstorage = conn->lstorage;
+ struct sieve_storage *storage = &lstorage->storage;
+ const struct sieve_ldap_storage_settings *set = &lstorage->set;
+ struct sieve_ldap_script_lookup_request *request;
+ const struct var_expand_table *tab;
+ char **attr_names;
+ const char *error;
+ string_t *str;
+
+ pool_t pool = pool_alloconly_create
+ ("sieve_ldap_script_lookup_request", 512);
+ request = p_new(pool, struct sieve_ldap_script_lookup_request, 1);
+ request->request.pool = pool;
+
+ tab = db_ldap_get_var_expand_table(conn, name);
+
+ str = t_str_new(512);
+ if (var_expand(str, set->base, tab, &error) <= 0) {
+ e_error(storage->event, "db: "
+ "Failed to expand base=%s: %s",
+ set->base, error);
+ return -1;
+ }
+ request->request.base = p_strdup(pool, str_c(str));
+
+ attr_names = p_new(pool, char *, 3);
+ attr_names[0] = p_strdup(pool, set->sieve_ldap_mod_attr);
+
+ str_truncate(str, 0);
+ if (var_expand(str, set->sieve_ldap_filter, tab, &error) <= 0) {
+ e_error(storage->event, "db: "
+ "Failed to expand sieve_ldap_filter=%s: %s",
+ set->sieve_ldap_filter, error);
+ return -1;
+ }
+
+ request->request.scope = lstorage->set.ldap_scope;
+ request->request.filter = p_strdup(pool, str_c(str));
+ request->request.attributes = attr_names;
+
+ e_debug(storage->event, "base=%s scope=%s filter=%s fields=%s",
+ request->request.base, lstorage->set.scope,
+ request->request.filter,
+ t_strarray_join((const char **)attr_names, ","));
+
+ request->request.callback = sieve_ldap_lookup_script_callback;
+ db_ldap_request(conn, &request->request);
+ db_ldap_wait(conn);
+
+ *dn_r = t_strdup(request->result_dn);
+ *modattr_r = t_strdup(request->result_modattr);
+ pool_unref(&request->request.pool);
+ return (*dn_r == NULL ? 0 : 1);
+}
+
+struct sieve_ldap_script_read_request {
+ struct ldap_request request;
+
+ unsigned int entries;
+ struct istream *result;
+};
+
+static void
+sieve_ldap_read_script_callback(struct ldap_connection *conn,
+ struct ldap_request *request, LDAPMessage *res)
+{
+ struct sieve_storage *storage = &conn->lstorage->storage;
+ struct sieve_ldap_script_read_request *srequest =
+ (struct sieve_ldap_script_read_request *)request;
+
+ if (res == NULL) {
+ io_loop_stop(conn->ioloop);
+ return;
+ }
+
+ if (ldap_msgtype(res) != LDAP_RES_SEARCH_RESULT) {
+
+ if (srequest->result == NULL) {
+ (void)sieve_ldap_db_get_script(conn, res, &srequest->result);
+ } else {
+ e_error(storage->event, "db: "
+ "Search returned more than one entry for Sieve script DN");
+ i_stream_unref(&srequest->result);
+ }
+
+ } else {
+ io_loop_stop(conn->ioloop);
+ return;
+ }
+}
+
+int sieve_ldap_db_read_script(struct ldap_connection *conn,
+ const char *dn, struct istream **script_r)
+{
+ struct sieve_ldap_storage *lstorage = conn->lstorage;
+ struct sieve_storage *storage = &lstorage->storage;
+ const struct sieve_ldap_storage_settings *set = &lstorage->set;
+ struct sieve_ldap_script_read_request *request;
+ char **attr_names;
+
+ pool_t pool = pool_alloconly_create
+ ("sieve_ldap_script_read_request", 512);
+ request = p_new(pool, struct sieve_ldap_script_read_request, 1);
+ request->request.pool = pool;
+ request->request.base = p_strdup(pool, dn);
+
+ attr_names = p_new(pool, char *, 3);
+ attr_names[0] = p_strdup(pool, set->sieve_ldap_script_attr);
+
+ request->request.scope = LDAP_SCOPE_BASE;
+ request->request.filter = "(objectClass=*)";
+ request->request.attributes = attr_names;
+
+ e_debug(storage->event, "base=%s scope=base filter=%s fields=%s",
+ request->request.base, request->request.filter,
+ t_strarray_join((const char **)attr_names, ","));
+
+ request->request.callback = sieve_ldap_read_script_callback;
+ db_ldap_request(conn, &request->request);
+ db_ldap_wait(conn);
+
+ *script_r = request->result;
+ pool_unref(&request->request.pool);
+ return (*script_r == NULL ? 0 : 1);
+}
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.h b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.h
new file mode 100644
index 0000000..d213026
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-db.h
@@ -0,0 +1,140 @@
+#ifndef DB_LDAP_H
+#define DB_LDAP_H
+
+/* Functions like ldap_bind() have been deprecated in OpenLDAP 2.3
+ This define enables them until the code here can be refactored */
+#define LDAP_DEPRECATED 1
+
+/* Maximum number of pending requests before delaying new requests. */
+#define DB_LDAP_MAX_PENDING_REQUESTS 8
+/* If LDAP connection is down, fail requests after waiting for this long. */
+#define DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS 4
+/* If request is still in queue after this many seconds and other requests
+ have been replied, assume the request was lost and abort it. */
+#define DB_LDAP_REQUEST_LOST_TIMEOUT_SECS 60
+/* If server disconnects us, don't reconnect if no requests have been sent
+ for this many seconds. */
+#define DB_LDAP_IDLE_RECONNECT_SECS 60
+
+#include <ldap.h>
+
+#define HAVE_LDAP_SASL
+#ifdef HAVE_SASL_SASL_H
+# include <sasl/sasl.h>
+#elif defined (HAVE_SASL_H)
+# include <sasl.h>
+#else
+# undef HAVE_LDAP_SASL
+#endif
+#ifdef LDAP_OPT_X_TLS
+# define OPENLDAP_TLS_OPTIONS
+#endif
+#if !defined(SASL_VERSION_MAJOR) || SASL_VERSION_MAJOR < 2
+# undef HAVE_LDAP_SASL
+#endif
+
+#ifndef LDAP_SASL_QUIET
+# define LDAP_SASL_QUIET 0 /* Doesn't exist in Solaris LDAP */
+#endif
+
+/* Older versions may require calling ldap_result() twice */
+#if LDAP_VENDOR_VERSION <= 20112
+# define OPENLDAP_ASYNC_WORKAROUND
+#endif
+
+/* Solaris LDAP library doesn't have LDAP_OPT_SUCCESS */
+#ifndef LDAP_OPT_SUCCESS
+# define LDAP_OPT_SUCCESS LDAP_SUCCESS
+#endif
+
+struct ldap_connection;
+struct ldap_request;
+
+typedef void db_search_callback_t(struct ldap_connection *conn,
+ struct ldap_request *request,
+ LDAPMessage *res);
+struct ldap_request {
+ pool_t pool;
+
+ /* msgid for sent requests, -1 if not sent */
+ int msgid;
+ /* timestamp when request was created */
+ time_t create_time;
+
+ bool failed;
+
+ db_search_callback_t *callback;
+
+ const char *base;
+ const char *filter;
+ int scope;
+ char **attributes;
+
+ struct db_ldap_result *result;
+};
+
+enum ldap_connection_state {
+ /* Not connected */
+ LDAP_CONN_STATE_DISCONNECTED,
+ /* Binding - either to default dn or doing auth bind */
+ LDAP_CONN_STATE_BINDING,
+ /* Bound */
+ LDAP_CONN_STATE_BOUND
+};
+
+struct ldap_connection {
+ struct ldap_connection *next;
+
+ struct sieve_ldap_storage *lstorage;
+
+ pool_t pool;
+ int refcount;
+
+ LDAP *ld;
+ enum ldap_connection_state conn_state;
+ int default_bind_msgid;
+
+ int fd;
+ struct io *io;
+ struct timeout *to;
+ struct ioloop *ioloop;
+
+ /* Request queue contains sent requests at tail (msgid != -1) and
+ queued requests at head (msgid == -1). */
+ struct aqueue *request_queue;
+ ARRAY(struct ldap_request *) request_array;
+ /* Number of messages in queue with msgid != -1 */
+ unsigned int pending_count;
+
+ /* Timestamp when we last received a reply */
+ time_t last_reply_stamp;
+};
+
+
+int ldap_deref_from_str(const char *str, int *deref_r);
+int ldap_scope_from_str(const char *str, int *scope_r);
+#ifdef OPENLDAP_TLS_OPTIONS
+int ldap_tls_require_cert_from_str(const char *str, int *opt_x_tls_r);
+#endif
+
+/* Send/queue request */
+void db_ldap_request(struct ldap_connection *conn,
+ struct ldap_request *request);
+
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
+const char *ldap_escape(const char *str);
+const char *ldap_get_error(struct ldap_connection *conn);
+
+int sieve_ldap_db_connect(struct ldap_connection *conn);
+
+struct ldap_connection *
+sieve_ldap_db_init(struct sieve_ldap_storage *lstorage);
+void sieve_ldap_db_unref(struct ldap_connection **conn);
+
+int sieve_ldap_db_lookup_script(struct ldap_connection *conn,
+ const char *name, const char **dn_r, const char **modattr_r);
+int sieve_ldap_db_read_script(struct ldap_connection *conn,
+ const char *dn, struct istream **script_r);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-script.c b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-script.c
new file mode 100644
index 0000000..1c732db
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-script.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "time-util.h"
+#include "istream.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "str.h"
+#include "strfuncs.h"
+
+#include "sieve-error.h"
+#include "sieve-dump.h"
+#include "sieve-binary.h"
+
+/*
+ * Script file implementation
+ */
+
+static struct sieve_ldap_script *sieve_ldap_script_alloc(void)
+{
+ struct sieve_ldap_script *lscript;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_ldap_script", 1024);
+ lscript = p_new(pool, struct sieve_ldap_script, 1);
+ lscript->script = sieve_ldap_script;
+ lscript->script.pool = pool;
+
+ return lscript;
+}
+
+struct sieve_ldap_script *sieve_ldap_script_init
+(struct sieve_ldap_storage *lstorage, const char *name)
+{
+ struct sieve_storage *storage = &lstorage->storage;
+ struct sieve_ldap_script *lscript = NULL;
+ const char *location;
+
+ if ( name == NULL ) {
+ name = SIEVE_LDAP_SCRIPT_DEFAULT;
+ location = storage->location;
+ } else {
+ location = t_strconcat
+ (storage->location, ";name=", name, NULL);
+ }
+
+ lscript = sieve_ldap_script_alloc();
+ sieve_script_init(&lscript->script,
+ storage, &sieve_ldap_script, location, name);
+ return lscript;
+}
+
+static int sieve_ldap_script_open
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+ int ret;
+
+ if ( sieve_ldap_db_connect(lstorage->conn) < 0 ) {
+ sieve_storage_set_critical(storage,
+ "Failed to connect to LDAP database");
+ *error_r = storage->error_code;
+ return -1;
+ }
+
+ if ( (ret=sieve_ldap_db_lookup_script(lstorage->conn,
+ script->name, &lscript->dn, &lscript->modattr)) <= 0 ) {
+ if ( ret == 0 ) {
+ e_debug(script->event, "Script entry not found");
+ sieve_script_set_error(script,
+ SIEVE_ERROR_NOT_FOUND,
+ "Sieve script not found");
+ } else {
+ sieve_script_set_internal_error(script);
+ }
+ *error_r = script->storage->error_code;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sieve_ldap_script_get_stream
+(struct sieve_script *script, struct istream **stream_r,
+ enum sieve_error *error_r)
+{
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+ struct sieve_storage *storage = script->storage;
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+ int ret;
+
+ i_assert(lscript->dn != NULL);
+
+ if ( (ret=sieve_ldap_db_read_script(
+ lstorage->conn, lscript->dn, stream_r)) <= 0 ) {
+ if ( ret == 0 ) {
+ e_debug(script->event, "Script attribute not found");
+ sieve_script_set_error(script,
+ SIEVE_ERROR_NOT_FOUND,
+ "Sieve script not found");
+ } else {
+ sieve_script_set_internal_error(script);
+ }
+ *error_r = script->storage->error_code;
+ return -1;
+ }
+ return 0;
+}
+
+static int sieve_ldap_script_binary_read_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock,
+ sieve_size_t *offset)
+{
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+ struct sieve_instance *svinst = script->storage->svinst;
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)script->storage;
+ struct sieve_binary *sbin =
+ sieve_binary_block_get_binary(sblock);
+ time_t bmtime = sieve_binary_mtime(sbin);
+ string_t *dn, *modattr;
+
+ /* config file changed? */
+ if ( bmtime <= lstorage->set_mtime ) {
+ if ( svinst->debug ) {
+ e_debug(script->event,
+ "Sieve binary `%s' is not newer "
+ "than the LDAP configuration `%s' (%s <= %s)",
+ sieve_binary_path(sbin), lstorage->config_file,
+ t_strflocaltime("%Y-%m-%d %H:%M:%S", bmtime),
+ t_strflocaltime("%Y-%m-%d %H:%M:%S", lstorage->set_mtime));
+ }
+ return 0;
+ }
+
+ /* open script if not open already */
+ if ( lscript->dn == NULL &&
+ sieve_script_open(script, NULL) < 0 )
+ return 0;
+
+ /* if modattr not found, recompile always */
+ if ( lscript->modattr == NULL || *lscript->modattr == '\0' ) {
+ e_error(script->event,
+ "LDAP entry for script `%s' "
+ "has no modified attribute `%s'",
+ sieve_script_location(script),
+ lstorage->set.sieve_ldap_mod_attr);
+ return 0;
+ }
+
+ /* compare DN in binary and from search result */
+ if ( !sieve_binary_read_string(sblock, offset, &dn) ) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s': "
+ "Invalid DN",
+ sieve_binary_path(sbin), sieve_script_location(script));
+ return -1;
+ }
+ i_assert( lscript->dn != NULL );
+ if ( strcmp(str_c(dn), lscript->dn) != 0 ) {
+ e_debug(script->event,
+ "Binary `%s' reports different LDAP DN for script `%s' "
+ "(`%s' rather than `%s')",
+ sieve_binary_path(sbin), sieve_script_location(script),
+ str_c(dn), lscript->dn);
+ return 0;
+ }
+
+ /* compare modattr in binary and from search result */
+ if ( !sieve_binary_read_string(sblock, offset, &modattr) ) {
+ e_error(script->event,
+ "Binary `%s' has invalid metadata for script `%s': "
+ "Invalid modified attribute",
+ sieve_binary_path(sbin), sieve_script_location(script));
+ return -1;
+ }
+ if ( strcmp(str_c(modattr), lscript->modattr) != 0 ) {
+ e_debug(script->event,
+ "Binary `%s' reports different modified attribute content "
+ "for script `%s' (`%s' rather than `%s')",
+ sieve_binary_path(sbin), sieve_script_location(script),
+ str_c(modattr), lscript->modattr);
+ return 0;
+ }
+ return 1;
+}
+
+static void sieve_ldap_script_binary_write_metadata
+(struct sieve_script *script, struct sieve_binary_block *sblock)
+{
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+
+ sieve_binary_emit_cstring(sblock, lscript->dn);
+ if (lscript->modattr == NULL)
+ sieve_binary_emit_cstring(sblock, "");
+ else
+ sieve_binary_emit_cstring(sblock, lscript->modattr);
+}
+
+static bool sieve_ldap_script_binary_dump_metadata
+(struct sieve_script *script ATTR_UNUSED, struct sieve_dumptime_env *denv,
+ struct sieve_binary_block *sblock, sieve_size_t *offset)
+{
+ string_t *dn, *modattr;
+
+ if ( !sieve_binary_read_string(sblock, offset, &dn) )
+ return FALSE;
+ sieve_binary_dumpf(denv, "ldap.dn = %s\n", str_c(dn));
+
+ if ( !sieve_binary_read_string(sblock, offset, &modattr) )
+ return FALSE;
+ sieve_binary_dumpf(denv, "ldap.mod_attr = %s\n", str_c(modattr));
+
+ return TRUE;
+}
+
+static const char *sieve_ldap_script_get_binpath
+(struct sieve_ldap_script *lscript)
+{
+ struct sieve_script *script = &lscript->script;
+ struct sieve_storage *storage = script->storage;
+
+ if ( lscript->binpath == NULL ) {
+ if ( storage->bin_dir == NULL )
+ return NULL;
+ lscript->binpath = p_strconcat(script->pool,
+ storage->bin_dir, "/",
+ sieve_binfile_from_name(script->name), NULL);
+ }
+
+ return lscript->binpath;
+}
+
+static struct sieve_binary *sieve_ldap_script_binary_load
+(struct sieve_script *script, enum sieve_error *error_r)
+{
+ struct sieve_storage *storage = script->storage;
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+
+ if ( sieve_ldap_script_get_binpath(lscript) == NULL )
+ return NULL;
+
+ return sieve_binary_open(storage->svinst,
+ lscript->binpath, script, error_r);
+}
+
+static int sieve_ldap_script_binary_save
+(struct sieve_script *script, struct sieve_binary *sbin, bool update,
+ enum sieve_error *error_r)
+{
+ struct sieve_ldap_script *lscript =
+ (struct sieve_ldap_script *)script;
+
+ if ( sieve_ldap_script_get_binpath(lscript) == NULL )
+ return 0;
+
+ if ( sieve_storage_setup_bindir(script->storage, 0700) < 0 )
+ return -1;
+
+ return sieve_binary_save
+ (sbin, lscript->binpath, update, 0600, error_r);
+}
+
+static bool sieve_ldap_script_equals
+(const struct sieve_script *script, const struct sieve_script *other)
+{
+ struct sieve_storage *storage = script->storage;
+ struct sieve_storage *sother = other->storage;
+
+ if ( strcmp(storage->location, sother->location) != 0 )
+ return FALSE;
+
+ i_assert( script->name != NULL && other->name != NULL );
+
+ return ( strcmp(script->name, other->name) == 0 );
+}
+
+const struct sieve_script sieve_ldap_script = {
+ .driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME,
+ .v = {
+ .open = sieve_ldap_script_open,
+
+ .get_stream = sieve_ldap_script_get_stream,
+
+ .binary_read_metadata = sieve_ldap_script_binary_read_metadata,
+ .binary_write_metadata = sieve_ldap_script_binary_write_metadata,
+ .binary_dump_metadata = sieve_ldap_script_binary_dump_metadata,
+ .binary_load = sieve_ldap_script_binary_load,
+ .binary_save = sieve_ldap_script_binary_save,
+
+ .equals = sieve_ldap_script_equals
+ }
+};
+
+/*
+ * Script sequence
+ */
+
+struct sieve_ldap_script_sequence {
+ struct sieve_script_sequence seq;
+
+ bool done:1;
+};
+
+struct sieve_script_sequence *sieve_ldap_storage_get_script_sequence
+(struct sieve_storage *storage, enum sieve_error *error_r)
+{
+ struct sieve_ldap_script_sequence *lsec = NULL;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+
+ /* Create sequence object */
+ lsec = i_new(struct sieve_ldap_script_sequence, 1);
+ sieve_script_sequence_init(&lsec->seq, storage);
+
+ return &lsec->seq;
+}
+
+struct sieve_script *sieve_ldap_script_sequence_next
+(struct sieve_script_sequence *seq, enum sieve_error *error_r)
+{
+ struct sieve_ldap_script_sequence *lsec =
+ (struct sieve_ldap_script_sequence *)seq;
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)seq->storage;
+ struct sieve_ldap_script *lscript;
+
+ if ( error_r != NULL )
+ *error_r = SIEVE_ERROR_NONE;
+
+ if ( lsec->done )
+ return NULL;
+ lsec->done = TRUE;
+
+ lscript = sieve_ldap_script_init
+ (lstorage, seq->storage->script_name);
+ if ( sieve_script_open(&lscript->script, error_r) < 0 ) {
+ struct sieve_script *script = &lscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return &lscript->script;
+}
+
+void sieve_ldap_script_sequence_destroy
+(struct sieve_script_sequence *seq)
+{
+ struct sieve_ldap_script_sequence *lsec =
+ (struct sieve_ldap_script_sequence *)seq;
+ i_free(lsec);
+}
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage-settings.c b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage-settings.c
new file mode 100644
index 0000000..35fe5cb
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage-settings.c
@@ -0,0 +1,169 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "env-util.h"
+#include "settings.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-error.h"
+
+#include "sieve-ldap-db.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, sieve_ldap_storage_settings)
+#define DEF_INT(name) DEF_STRUCT_INT(name, sieve_ldap_storage_settings)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, sieve_ldap_storage_settings)
+
+static struct setting_def setting_defs[] = {
+ DEF_STR(hosts),
+ DEF_STR(uris),
+ DEF_STR(dn),
+ DEF_STR(dnpass),
+ DEF_BOOL(tls),
+ DEF_BOOL(sasl_bind),
+ DEF_STR(sasl_mech),
+ DEF_STR(sasl_realm),
+ DEF_STR(sasl_authz_id),
+ DEF_STR(tls_ca_cert_file),
+ DEF_STR(tls_ca_cert_dir),
+ DEF_STR(tls_cert_file),
+ DEF_STR(tls_key_file),
+ DEF_STR(tls_cipher_suite),
+ DEF_STR(tls_require_cert),
+ DEF_STR(deref),
+ DEF_STR(scope),
+ DEF_STR(base),
+ DEF_INT(ldap_version),
+ DEF_STR(debug_level),
+ DEF_STR(ldaprc_path),
+ DEF_STR(sieve_ldap_script_attr),
+ DEF_STR(sieve_ldap_mod_attr),
+ DEF_STR(sieve_ldap_filter),
+
+ { 0, NULL, 0 }
+};
+
+static struct sieve_ldap_storage_settings default_settings = {
+ .hosts = NULL,
+ .uris = NULL,
+ .dn = NULL,
+ .dnpass = NULL,
+ .tls = FALSE,
+ .sasl_bind = FALSE,
+ .sasl_mech = NULL,
+ .sasl_realm = NULL,
+ .sasl_authz_id = NULL,
+ .tls_ca_cert_file = NULL,
+ .tls_ca_cert_dir = NULL,
+ .tls_cert_file = NULL,
+ .tls_key_file = NULL,
+ .tls_cipher_suite = NULL,
+ .tls_require_cert = NULL,
+ .deref = "never",
+ .scope = "subtree",
+ .base = NULL,
+ .ldap_version = 3,
+ .debug_level = "0",
+ .ldaprc_path = "",
+ .sieve_ldap_script_attr = "mailSieveRuleSource",
+ .sieve_ldap_mod_attr = "modifyTimestamp",
+ .sieve_ldap_filter = "(&(objectClass=posixAccount)(uid=%u))",
+};
+
+static const char *parse_setting(const char *key, const char *value,
+ struct sieve_ldap_storage *lstorage)
+{
+ return parse_setting_from_defs
+ (lstorage->storage.pool, setting_defs, &lstorage->set, key, value);
+}
+
+int sieve_ldap_storage_read_settings
+(struct sieve_ldap_storage *lstorage, const char *config_path)
+{
+ struct sieve_storage *storage = &lstorage->storage;
+ const char *str, *error;
+ struct stat st;
+
+ if ( stat(config_path, &st) < 0 ) {
+ e_error(storage->event,
+ "Failed to read LDAP storage config: "
+ "stat(%s) failed: %m", config_path);
+ return -1;
+ }
+
+ lstorage->set = default_settings;
+ lstorage->set_mtime = st.st_mtime;
+
+ if (!settings_read_nosection
+ (config_path, parse_setting, lstorage, &error)) {
+ sieve_storage_set_critical(storage,
+ "Failed to read LDAP storage config `%s': %s",
+ config_path, error);
+ return -1;
+ }
+
+ if (lstorage->set.base == NULL) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "No search base given", config_path);
+ return -1;
+ }
+
+ if (lstorage->set.uris == NULL && lstorage->set.hosts == NULL) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "No uris or hosts set", config_path);
+ return -1;
+ }
+
+ if (*lstorage->set.ldaprc_path != '\0') {
+ str = getenv("LDAPRC");
+ if (str != NULL && strcmp(str, lstorage->set.ldaprc_path) != 0) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "Multiple different ldaprc_path settings not allowed "
+ "(%s and %s)", config_path, str, lstorage->set.ldaprc_path);
+ return -1;
+ }
+ env_put("LDAPRC", lstorage->set.ldaprc_path);
+ }
+
+ if ( ldap_deref_from_str
+ (lstorage->set.deref, &lstorage->set.ldap_deref) < 0 ) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "Invalid deref option `%s'",
+ config_path, lstorage->set.deref);;
+ }
+
+ if ( ldap_scope_from_str
+ (lstorage->set.scope, &lstorage->set.ldap_scope) < 0 ) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "Invalid scope option `%s'",
+ config_path, lstorage->set.scope);;
+ }
+
+#ifdef OPENLDAP_TLS_OPTIONS
+ if ( lstorage->set.tls_require_cert != NULL &&
+ ldap_tls_require_cert_from_str(lstorage->set.tls_require_cert,
+ &lstorage->set.ldap_tls_require_cert) < 0) {
+ sieve_storage_set_critical(storage,
+ "Invalid LDAP storage config `%s': "
+ "Invalid tls_require_cert option `%s'",
+ config_path, lstorage->set.tls_require_cert);
+ }
+#endif
+ return 0;
+}
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.c b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.c
new file mode 100644
index 0000000..1f7922a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.c
@@ -0,0 +1,230 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+//#include "ldap.h"
+
+#include "sieve-common.h"
+
+#include "sieve-ldap-storage.h"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-error.h"
+
+#ifndef PLUGIN_BUILD
+const struct sieve_storage sieve_ldap_storage;
+#else
+const struct sieve_storage sieve_ldap_storage_plugin;
+#endif
+
+/*
+ * Storage class
+ */
+
+static struct sieve_storage *sieve_ldap_storage_alloc(void)
+{
+ struct sieve_ldap_storage *lstorage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("sieve_ldap_storage", 1024);
+ lstorage = p_new(pool, struct sieve_ldap_storage, 1);
+#ifndef PLUGIN_BUILD
+ lstorage->storage = sieve_ldap_storage;
+#else
+ lstorage->storage = sieve_ldap_storage_plugin;
+#endif
+ lstorage->storage.pool = pool;
+
+ return &lstorage->storage;
+}
+
+static int sieve_ldap_storage_init
+(struct sieve_storage *storage, const char *const *options,
+ enum sieve_error *error_r)
+{
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+ struct sieve_instance *svinst = storage->svinst;
+ const char *username = NULL;
+
+ if ( options != NULL ) {
+ while ( *options != NULL ) {
+ const char *option = *options;
+
+ if ( strncasecmp(option, "user=", 5) == 0 && option[5] != '\0' ) {
+ username = option+5;
+ } else {
+ sieve_storage_set_critical(storage,
+ "Invalid option `%s'", option);
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+
+ options++;
+ }
+ }
+
+ if ( username == NULL ) {
+ if ( svinst->username == NULL ) {
+ sieve_storage_set_critical(storage,
+ "No username specified");
+ *error_r = SIEVE_ERROR_TEMP_FAILURE;
+ return -1;
+ }
+ username = svinst->username;
+ }
+
+ e_debug(storage->event, "user=%s, config=%s",
+ username, storage->location);
+
+ if ( sieve_ldap_storage_read_settings(lstorage, storage->location) < 0 )
+ return -1;
+
+ lstorage->username = p_strdup(storage->pool, username);
+ lstorage->config_file = p_strdup(storage->pool, storage->location);
+ lstorage->conn = sieve_ldap_db_init(lstorage);
+
+ storage->location = p_strconcat(storage->pool,
+ SIEVE_LDAP_STORAGE_DRIVER_NAME, ":", storage->location,
+ ";user=", username, NULL);
+
+ return 0;
+}
+
+static void sieve_ldap_storage_destroy
+(struct sieve_storage *storage)
+{
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+
+ if ( lstorage->conn != NULL )
+ sieve_ldap_db_unref(&lstorage->conn);
+}
+
+/*
+ * Script access
+ */
+
+static struct sieve_script *sieve_ldap_storage_get_script
+(struct sieve_storage *storage, const char *name)
+{
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+ struct sieve_ldap_script *lscript;
+
+ T_BEGIN {
+ lscript = sieve_ldap_script_init(lstorage, name);
+ } T_END;
+
+ return &lscript->script;
+}
+
+/*
+ * Active script
+ */
+
+struct sieve_script *sieve_ldap_storage_active_script_open
+(struct sieve_storage *storage)
+{
+ struct sieve_ldap_storage *lstorage =
+ (struct sieve_ldap_storage *)storage;
+ struct sieve_ldap_script *lscript;
+
+ lscript = sieve_ldap_script_init
+ (lstorage, storage->script_name);
+ if ( sieve_script_open(&lscript->script, NULL) < 0 ) {
+ struct sieve_script *script = &lscript->script;
+ sieve_script_unref(&script);
+ return NULL;
+ }
+
+ return &lscript->script;
+}
+
+int sieve_ldap_storage_active_script_get_name
+(struct sieve_storage *storage, const char **name_r)
+{
+ if ( storage->script_name != NULL )
+ *name_r = storage->script_name;
+ else
+ *name_r = SIEVE_LDAP_SCRIPT_DEFAULT;
+ return 0;
+}
+
+/*
+ * Driver definition
+ */
+
+#ifndef PLUGIN_BUILD
+const struct sieve_storage sieve_ldap_storage = {
+#else
+const struct sieve_storage sieve_ldap_storage_plugin = {
+#endif
+ .driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME,
+ .version = 0,
+ .v = {
+ .alloc = sieve_ldap_storage_alloc,
+ .init = sieve_ldap_storage_init,
+ .destroy = sieve_ldap_storage_destroy,
+
+ .get_script = sieve_ldap_storage_get_script,
+
+ .get_script_sequence = sieve_ldap_storage_get_script_sequence,
+ .script_sequence_next = sieve_ldap_script_sequence_next,
+ .script_sequence_destroy = sieve_ldap_script_sequence_destroy,
+
+ .active_script_get_name = sieve_ldap_storage_active_script_get_name,
+ .active_script_open = sieve_ldap_storage_active_script_open,
+
+ // FIXME: impement management interface
+ }
+};
+
+#ifndef SIEVE_BUILTIN_LDAP
+/* Building a plugin */
+
+const char *sieve_storage_ldap_plugin_version = PIGEONHOLE_ABI_VERSION;
+
+void sieve_storage_ldap_plugin_load
+(struct sieve_instance *svinst, void **context);
+void sieve_storage_ldap_plugin_unload
+(struct sieve_instance *svinst, void *context);
+void sieve_storage_ldap_plugin_init(void);
+void sieve_storage_ldap_plugin_deinit(void);
+
+void sieve_storage_ldap_plugin_load
+(struct sieve_instance *svinst, void **context ATTR_UNUSED)
+{
+ sieve_storage_class_register
+ (svinst, &sieve_ldap_storage_plugin);
+
+ e_debug(svinst->event,
+ "Sieve LDAP storage plugin for %s version %s loaded",
+ PIGEONHOLE_NAME, PIGEONHOLE_VERSION_FULL);
+}
+
+void sieve_storage_ldap_plugin_unload
+(struct sieve_instance *svinst ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ sieve_storage_class_unregister
+ (svinst, &sieve_ldap_storage_plugin);
+}
+
+void sieve_storage_ldap_plugin_init(void)
+{
+ /* Nothing */
+}
+
+void sieve_storage_ldap_plugin_deinit(void)
+{
+ /* Nothing */
+}
+#endif
+
+#else /* !defined(SIEVE_BUILTIN_LDAP) && !defined(PLUGIN_BUILD) */
+const struct sieve_storage sieve_ldap_storage = {
+ .driver_name = SIEVE_LDAP_STORAGE_DRIVER_NAME
+};
+#endif
diff --git a/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.h b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.h
new file mode 100644
index 0000000..f97ceba
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/storage/ldap/sieve-ldap-storage.h
@@ -0,0 +1,108 @@
+#ifndef SIEVE_LDAP_STORAGE_H
+#define SIEVE_LDAP_STORAGE_H
+
+#include "sieve.h"
+#include "sieve-script-private.h"
+#include "sieve-storage-private.h"
+
+#define SIEVE_LDAP_SCRIPT_DEFAULT "default"
+
+#if defined(SIEVE_BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#include "sieve-ldap-db.h"
+
+struct sieve_ldap_storage;
+
+/*
+ * LDAP settings
+ */
+
+struct sieve_ldap_storage_settings {
+ const char *hosts;
+ const char *uris;
+ const char *dn;
+ const char *dnpass;
+
+ bool tls;
+ bool sasl_bind;
+ const char *sasl_mech;
+ const char *sasl_realm;
+ const char *sasl_authz_id;
+
+ const char *tls_ca_cert_file;
+ const char *tls_ca_cert_dir;
+ const char *tls_cert_file;
+ const char *tls_key_file;
+ const char *tls_cipher_suite;
+ const char *tls_require_cert;
+
+ const char *deref;
+ const char *scope;
+ const char *base;
+ unsigned int ldap_version;
+
+ const char *ldaprc_path;
+ const char *debug_level;
+
+ const char *sieve_ldap_script_attr;
+ const char *sieve_ldap_mod_attr;
+ const char *sieve_ldap_filter;
+
+ /* ... */
+ int ldap_deref, ldap_scope, ldap_tls_require_cert;
+};
+
+int sieve_ldap_storage_read_settings
+ (struct sieve_ldap_storage *lstorage, const char *config_path);
+
+/*
+ * Storage class
+ */
+
+struct sieve_ldap_storage {
+ struct sieve_storage storage;
+
+ struct sieve_ldap_storage_settings set;
+ time_t set_mtime;
+
+ const char *config_file;
+ const char *username; // FIXME: needed?
+
+ struct ldap_connection *conn;
+};
+
+struct sieve_script *sieve_ldap_storage_active_script_open
+ (struct sieve_storage *storage);
+int sieve_ldap_storage_active_script_get_name
+ (struct sieve_storage *storage, const char **name_r);
+
+/*
+ * Script class
+ */
+
+struct sieve_ldap_script {
+ struct sieve_script script;
+
+ const char *dn;
+ const char *modattr;
+
+ const char *binpath;
+};
+
+struct sieve_ldap_script *sieve_ldap_script_init
+ (struct sieve_ldap_storage *lstorage, const char *name);
+
+/*
+ * Script sequence
+ */
+
+struct sieve_script_sequence *sieve_ldap_storage_get_script_sequence
+ (struct sieve_storage *storage, enum sieve_error *error_r);
+
+struct sieve_script *sieve_ldap_script_sequence_next
+ (struct sieve_script_sequence *seq, enum sieve_error *error_r);
+void sieve_ldap_script_sequence_destroy(struct sieve_script_sequence *seq);
+
+#endif
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/tst-address.c b/pigeonhole/src/lib-sieve/tst-address.c
new file mode 100644
index 0000000..086679d
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-address.c
@@ -0,0 +1,280 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-message.h"
+#include "sieve-address.h"
+#include "sieve-address-parts.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include <stdio.h>
+
+/*
+ * Address test
+ *
+ * Syntax:
+ * address [ADDRESS-PART] [COMPARATOR] [MATCH-TYPE]
+ * <header-list: string-list> <key-list: string-list>
+ */
+
+static bool tst_address_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_address_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_address_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_address = {
+ .identifier = "address",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_address_registered,
+ .validate = tst_address_validate,
+ .generate = tst_address_generate
+};
+
+/*
+ * Address operation
+ */
+
+static bool tst_address_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_address_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_address_operation = {
+ .mnemonic = "ADDRESS",
+ .code = SIEVE_OPERATION_ADDRESS,
+ .dump = tst_address_operation_dump,
+ .execute = tst_address_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_address_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag
+ (valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR );
+ sieve_match_types_link_tags
+ (valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+ sieve_address_parts_link_tags
+ (valdtr, cmd_reg, SIEVE_AM_OPT_ADDRESS_PART);
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+/* List of valid headers:
+ * Implementations MUST restrict the address test to headers that
+ * contain addresses, but MUST include at least From, To, Cc, Bcc,
+ * Sender, Resent-From, and Resent-To, and it SHOULD include any other
+ * header that utilizes an "address-list" structured header body.
+ *
+ * This list explicitly does not contain the envelope-to and return-path
+ * headers. The envelope test must be used to test against these addresses.
+ *
+ * FIXME: this restriction is somewhat odd. Sieve list advises to allow
+ * any other header as long as its content matches the address-list
+ * grammar.
+ */
+static const char * const _allowed_headers[] = {
+ /* Required */
+ "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to",
+
+ /* Additional (RFC 822 / RFC 2822) */
+ "reply-to", "resent-reply-to", "resent-sender", "resent-cc", "resent-bcc",
+
+ /* Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt) */
+ "for-approval", "for-handling", "for-comment", "apparently-to", "errors-to",
+ "delivered-to", "return-receipt-to", "x-admin", "read-receipt-to",
+ "x-confirm-reading-to", "return-receipt-requested",
+ "registered-mail-reply-requested-by", "mail-followup-to", "mail-reply-to",
+ "abuse-reports-to", "x-complaints-to", "x-report-abuse-to",
+
+ /* Undocumented */
+ "x-beenthere", "x-original-to",
+
+ NULL
+};
+
+static int _header_is_allowed
+(void *context ATTR_UNUSED, struct sieve_ast_argument *arg)
+{
+ if ( sieve_argument_is_string_literal(arg) ) {
+ const char *header = sieve_ast_strlist_strc(arg);
+
+ const char * const *hdsp = _allowed_headers;
+ while ( *hdsp != NULL ) {
+ if ( strcasecmp( *hdsp, header ) == 0 )
+ return 1;
+
+ hdsp++;
+ }
+
+ return 0;
+ }
+
+ return 1;
+}
+
+static bool tst_address_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_ast_argument *header;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "header list", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+ return FALSE;
+
+ /* Check if supplied header names are allowed
+ * FIXME: verify dynamic header names at runtime
+ */
+ header = arg;
+ if ( sieve_ast_stringlist_map
+ (&header, NULL, _header_is_allowed) <= 0 ) {
+ i_assert(header != NULL);
+ sieve_argument_validate_error(valdtr, header,
+ "specified header '%s' is not allowed for the address test",
+ str_sanitize(sieve_ast_strlist_strc(header), 64));
+ return FALSE;
+ }
+
+ /* Check key list */
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_address_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &tst_address_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_address_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "ADDRESS");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "header list") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_address_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_address_part addrp =
+ SIEVE_ADDRESS_PART_DEFAULT(all_address_part);
+ struct sieve_stringlist *hdr_list, *hdr_value_list, *value_list, *key_list;
+ struct sieve_address_list *addr_list;
+ ARRAY_TYPE(sieve_message_override) svmos;
+ int match, ret;
+
+ /* Read optional operands */
+ i_zero(&svmos);
+ if ( sieve_message_opr_optional_read
+ (renv, address, NULL, &ret, &addrp, &mcht, &cmp, &svmos) < 0 )
+ return ret;
+
+ /* Read header-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+ <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "address test");
+
+ /* Get header */
+ sieve_runtime_trace_descend(renv);
+ if ( (ret=sieve_message_get_header_fields
+ (renv, hdr_list, &svmos, FALSE, &hdr_value_list)) <= 0 )
+ return ret;
+ sieve_runtime_trace_ascend(renv);
+
+ /* Create value stringlist */
+ addr_list = sieve_header_address_list_create(renv, hdr_value_list);
+ value_list = sieve_address_part_stringlist_create(renv, &addrp, addr_list);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/tst-allof.c b/pigeonhole/src/lib-sieve/tst-allof.c
new file mode 100644
index 0000000..6d278b9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-allof.c
@@ -0,0 +1,108 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Allof test
+ *
+ * Syntax
+ * allof <tests: test-list>
+ */
+
+static bool tst_allof_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_allof_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *tst,
+ int *const_current, int const_new);
+
+const struct sieve_command_def tst_allof = {
+ .identifier = "allof",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 2,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate_const = tst_allof_validate_const,
+ .control_generate = tst_allof_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_allof_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+ if ( const_next == 0 ) {
+ *const_current = 0;
+ return FALSE;
+ }
+
+ if ( *const_current != -1 )
+ *const_current = const_next;
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_allof_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true)
+{
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ struct sieve_ast_node *test;
+ struct sieve_jumplist false_jumps;
+
+ if ( sieve_ast_test_count(ctx->ast_node) > 1 ) {
+ if ( jump_true ) {
+ /* Prepare jumplist */
+ sieve_jumplist_init_temp(&false_jumps, sblock);
+ }
+
+ test = sieve_ast_test_first(ctx->ast_node);
+ while ( test != NULL ) {
+ bool result;
+
+ /* If this test list must jump on false, all sub-tests can simply add their jumps
+ * to the caller's jump list, otherwise this test redirects all false jumps to the
+ * end of the currently generated code. This is just after a final jump to the true
+ * case
+ */
+ if ( jump_true )
+ result = sieve_generate_test(cgenv, test, &false_jumps, FALSE);
+ else
+ result = sieve_generate_test(cgenv, test, jumps, FALSE);
+
+ if ( !result ) return FALSE;
+
+ test = sieve_ast_test_next(test);
+ }
+
+ if ( jump_true ) {
+ /* All tests succeeded, jump to case TRUE */
+ sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+ sieve_jumplist_add(jumps, sieve_binary_emit_offset(sblock, 0));
+
+ /* All false exits jump here */
+ sieve_jumplist_resolve(&false_jumps);
+ }
+ } else {
+ /* Script author is being inefficient; we can optimize the allof test away */
+ test = sieve_ast_test_first(ctx->ast_node);
+ sieve_generate_test(cgenv, test, jumps, jump_true);
+ }
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/tst-anyof.c b/pigeonhole/src/lib-sieve/tst-anyof.c
new file mode 100644
index 0000000..84bfedd
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-anyof.c
@@ -0,0 +1,107 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-generator.h"
+#include "sieve-validator.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+
+/*
+ * Anyof test
+ *
+ * Syntax
+ * anyof <tests: test-list>
+ */
+
+static bool tst_anyof_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_anyof_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *tst,
+ int *const_current, int const_next);
+
+const struct sieve_command_def tst_anyof = {
+ .identifier = "anyof",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 2,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate_const = tst_anyof_validate_const,
+ .control_generate = tst_anyof_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_anyof_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+ if ( const_next > 0 ) {
+ *const_current = 1;
+ return FALSE;
+ }
+
+ if ( *const_current != -1 )
+ *const_current = const_next;
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_anyof_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true)
+{
+ struct sieve_binary_block *sblock = cgenv->sblock;
+ struct sieve_ast_node *test;
+ struct sieve_jumplist true_jumps;
+
+ if ( sieve_ast_test_count(ctx->ast_node) > 1 ) {
+ if ( !jump_true ) {
+ /* Prepare jumplist */
+ sieve_jumplist_init_temp(&true_jumps, sblock);
+ }
+
+ test = sieve_ast_test_first(ctx->ast_node);
+ while ( test != NULL ) {
+ bool result;
+
+ /* If this test list must jump on true, all sub-tests can simply add their jumps
+ * to the caller's jump list, otherwise this test redirects all true jumps to the
+ * end of the currently generated code. This is just after a final jump to the false
+ * case
+ */
+ if ( !jump_true )
+ result = sieve_generate_test(cgenv, test, &true_jumps, TRUE);
+ else
+ result = sieve_generate_test(cgenv, test, jumps, TRUE);
+
+ if ( !result ) return FALSE;
+
+ test = sieve_ast_test_next(test);
+ }
+
+ if ( !jump_true ) {
+ /* All tests failed, jump to case FALSE */
+ sieve_operation_emit(sblock, NULL, &sieve_jmp_operation);
+ sieve_jumplist_add(jumps, sieve_binary_emit_offset(sblock, 0));
+
+ /* All true exits jump here */
+ sieve_jumplist_resolve(&true_jumps);
+ }
+ } else {
+ /* Script author is being inefficient; we can optimize the allof test away */
+ test = sieve_ast_test_first(ctx->ast_node);
+ sieve_generate_test(cgenv, test, jumps, jump_true);
+ }
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/lib-sieve/tst-exists.c b/pigeonhole/src/lib-sieve/tst-exists.c
new file mode 100644
index 0000000..0668c30
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-exists.c
@@ -0,0 +1,179 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+
+/*
+ * Exists test
+ *
+ * Syntax:
+ * exists <header-names: string-list>
+ */
+
+static bool tst_exists_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_exists_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_exists = {
+ .identifier = "exists",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_exists_validate,
+ .generate = tst_exists_generate
+};
+
+/*
+ * Exists operation
+ */
+
+static bool tst_exists_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_exists_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_exists_operation = {
+ .mnemonic = "EXISTS",
+ .code = SIEVE_OPERATION_EXISTS,
+ .dump = tst_exists_operation_dump,
+ .execute = tst_exists_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_exists_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "header names", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ return sieve_command_verify_headers_argument(valdtr, arg);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_exists_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &tst_exists_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_exists_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "EXISTS");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return sieve_opr_stringlist_dump(denv, address, "header names");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_exists_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_stringlist *hdr_list;
+ ARRAY_TYPE(sieve_message_override) svmos;
+ string_t *hdr_item;
+ bool matched;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+ i_zero(&svmos);
+ if ( sieve_message_opr_optional_read
+ (renv, address, NULL, &ret, NULL, NULL, NULL, &svmos) < 0 )
+ return ret;
+
+ /* Read header-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perfrom test
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "exists test");
+ sieve_runtime_trace_descend(renv);
+
+ /* Iterate through all requested headers to match (must find all specified) */
+ hdr_item = NULL;
+ matched = TRUE;
+ while ( matched &&
+ (ret=sieve_stringlist_next_item(hdr_list, &hdr_item)) > 0 ) {
+ struct sieve_stringlist *value_list;
+ string_t *dummy;
+
+ /* Get header */
+ if ( (ret=sieve_message_get_header_fields
+ (renv, sieve_single_stringlist_create(renv, hdr_item, FALSE),
+ &svmos, FALSE, &value_list)) <= 0 )
+ return ret;
+
+ if ( (ret=sieve_stringlist_next_item(value_list, &dummy)) < 0)
+ return value_list->exec_status;
+
+ if ( ret == 0 )
+ matched = FALSE;
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+ "header `%s' %s", str_sanitize(str_c(hdr_item), 80),
+ ( matched ? "exists" : "is missing" ));
+ }
+
+ if ( matched )
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, "all headers exist");
+ else
+ sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING, "headers are missing");
+
+ /* Set test result for subsequent conditional jump */
+ if ( ret >= 0 ) {
+ sieve_interpreter_set_test_result(renv->interp, matched);
+ return SIEVE_EXEC_OK;
+ }
+
+ sieve_runtime_trace_error(renv, "invalid header-list item");
+ return SIEVE_EXEC_BIN_CORRUPT;
+}
diff --git a/pigeonhole/src/lib-sieve/tst-header.c b/pigeonhole/src/lib-sieve/tst-header.c
new file mode 100644
index 0000000..6e553a2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-header.c
@@ -0,0 +1,204 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+/*
+ * Header test
+ *
+ * Syntax:
+ * header [COMPARATOR] [MATCH-TYPE]
+ * <header-names: string-list> <key-list: string-list>
+ */
+
+static bool tst_header_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_header_validate
+ (struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool tst_header_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_header = {
+ .identifier = "header",
+ .type = SCT_TEST,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_header_registered,
+ .validate = tst_header_validate,
+ .generate = tst_header_generate
+};
+
+/*
+ * Header operation
+ */
+
+static bool tst_header_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_header_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def tst_header_operation = {
+ .mnemonic = "HEADER",
+ .code = SIEVE_OPERATION_HEADER,
+ .dump = tst_header_operation_dump,
+ .execute = tst_header_operation_execute
+};
+
+/*
+ * Test registration
+ */
+
+static bool tst_header_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_header_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "header names", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ if ( !sieve_command_verify_headers_argument(valdtr, arg) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+ return FALSE;
+
+ /* Validate the key argument to a specified match type */
+ return sieve_match_type_validate
+ (valdtr, tst, arg, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_header_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, NULL, &tst_header_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_header_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "HEADER");
+ sieve_code_descend(denv);
+
+ /* Optional operands */
+ if ( sieve_message_opr_optional_dump(denv, address, NULL) != 0 )
+ return FALSE;
+
+ return
+ sieve_opr_stringlist_dump(denv, address, "header names") &&
+ sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Code execution
+ */
+
+static int tst_header_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_comparator cmp =
+ SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
+ struct sieve_match_type mcht =
+ SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_stringlist *hdr_list, *key_list, *value_list;
+ ARRAY_TYPE(sieve_message_override) svmos;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+ i_zero(&svmos);
+ if ( sieve_message_opr_optional_read
+ (renv, address, NULL, &ret, NULL, &mcht, &cmp, &svmos) < 0 )
+ return ret;
+
+ /* Read header-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "header-list", &hdr_list))
+ <= 0 )
+ return ret;
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform test
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "header test");
+
+ /* Get header */
+ sieve_runtime_trace_descend(renv);
+ if ( (ret=sieve_message_get_header_fields
+ (renv, hdr_list, &svmos, TRUE, &value_list)) <= 0 )
+ return ret;
+ sieve_runtime_trace_ascend(renv);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/tst-not.c b/pigeonhole/src/lib-sieve/tst-not.c
new file mode 100644
index 0000000..9687d49
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-not.c
@@ -0,0 +1,67 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+
+/*
+ * Not test
+ *
+ * Syntax:
+ * not <tests: test-list>
+ */
+
+static bool tst_not_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true);
+static bool tst_not_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *tst,
+ int *const_current, int const_next);
+
+const struct sieve_command_def tst_not = {
+ .identifier = "not",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 1,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate_const = tst_not_validate_const,
+ .control_generate = tst_not_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_not_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED, int *const_current, int const_next)
+{
+ if ( const_next < 0 )
+ *const_current = -1;
+ else if ( const_next > 0 )
+ *const_current = 0;
+ else
+ *const_current = 1;
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_not_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx,
+ struct sieve_jumplist *jumps, bool jump_true)
+{
+ struct sieve_ast_node *test;
+
+ /* Validator verified the existance of the single test already */
+ test = sieve_ast_test_first(ctx->ast_node);
+
+ return sieve_generate_test(cgenv, test, jumps, !jump_true);
+}
+
diff --git a/pigeonhole/src/lib-sieve/tst-size.c b/pigeonhole/src/lib-sieve/tst-size.c
new file mode 100644
index 0000000..1ed101b
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-size.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+/*
+ * Size test
+ *
+ * Syntax:
+ * size <":over" / ":under"> <limit: number>
+ */
+
+static bool
+tst_size_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+tst_size_pre_validate(struct sieve_validator *valdtr,
+ struct sieve_command *tst);
+static bool
+tst_size_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
+static bool
+tst_size_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def tst_size = {
+ .identifier = "size",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_size_registered,
+ .pre_validate = tst_size_pre_validate,
+ .validate = tst_size_validate,
+ .generate = tst_size_generate
+};
+
+/*
+ * Size operations
+ */
+
+static bool
+tst_size_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+tst_size_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def tst_size_over_operation = {
+ .mnemonic = "SIZE-OVER",
+ .code = SIEVE_OPERATION_SIZE_OVER,
+ .dump = tst_size_operation_dump,
+ .execute = tst_size_operation_execute
+};
+
+const struct sieve_operation_def tst_size_under_operation = {
+ .mnemonic = "SIZE-UNDER",
+ .code = SIEVE_OPERATION_SIZE_UNDER,
+ .dump = tst_size_operation_dump,
+ .execute = tst_size_operation_execute
+};
+
+/*
+ * Context data
+ */
+
+struct tst_size_context_data {
+ enum { SIZE_UNASSIGNED, SIZE_UNDER, SIZE_OVER } type;
+};
+
+#define TST_SIZE_ERROR_DUP_TAG \
+ "exactly one of the ':under' or ':over' tags must be specified " \
+ "for the size test, but more were found"
+
+/*
+ * Tag validation
+ */
+
+static bool
+tst_size_validate_over_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *tst)
+{
+ struct tst_size_context_data *ctx_data =
+ (struct tst_size_context_data *)tst->data;
+
+ if (ctx_data->type != SIZE_UNASSIGNED) {
+ sieve_argument_validate_error(valdtr, *arg,
+ TST_SIZE_ERROR_DUP_TAG);
+ return FALSE;
+ }
+
+ ctx_data->type = SIZE_OVER;
+
+ /* Delete this tag */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ return TRUE;
+}
+
+static bool
+tst_size_validate_under_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg ATTR_UNUSED,
+ struct sieve_command *tst)
+{
+ struct tst_size_context_data *ctx_data =
+ (struct tst_size_context_data *)tst->data;
+
+ if (ctx_data->type != SIZE_UNASSIGNED) {
+ sieve_argument_validate_error(valdtr, *arg,
+ TST_SIZE_ERROR_DUP_TAG);
+ return FALSE;
+ }
+
+ ctx_data->type = SIZE_UNDER;
+
+ /* Delete this tag */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ return TRUE;
+}
+
+/*
+ * Test registration
+ */
+
+static const struct sieve_argument_def size_over_tag = {
+ .identifier = "over",
+ .validate = tst_size_validate_over_tag
+};
+
+static const struct sieve_argument_def size_under_tag = {
+ .identifier = "under",
+ .validate = tst_size_validate_under_tag,
+};
+
+static bool
+tst_size_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* Register our tags */
+ sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_over_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_under_tag, 0);
+
+ return TRUE;
+}
+
+/*
+ * Test validation
+ */
+
+static bool
+tst_size_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst)
+{
+ struct tst_size_context_data *ctx_data;
+
+ /* Assign context */
+ ctx_data = p_new(sieve_command_pool(tst),
+ struct tst_size_context_data, 1);
+ ctx_data->type = SIZE_UNASSIGNED;
+ tst->data = ctx_data;
+
+ return TRUE;
+}
+
+static bool
+tst_size_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct tst_size_context_data *ctx_data =
+ (struct tst_size_context_data *)tst->data;
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if (ctx_data->type == SIZE_UNASSIGNED) {
+ sieve_command_validate_error(valdtr, tst,
+ "the size test requires either the :under or the :over tag "
+ "to be specified");
+ return FALSE;
+ }
+
+ if (!sieve_validate_positional_argument(valdtr, tst, arg, "limit", 1,
+ SAAT_NUMBER))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+bool tst_size_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *tst)
+{
+ struct tst_size_context_data *ctx_data =
+ (struct tst_size_context_data *)tst->data;
+
+ if (ctx_data->type == SIZE_OVER) {
+ sieve_operation_emit(cgenv->sblock, NULL,
+ &tst_size_over_operation);
+ } else {
+ sieve_operation_emit(cgenv->sblock, NULL,
+ &tst_size_under_operation);
+ }
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, tst, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+tst_size_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
+ sieve_code_descend(denv);
+
+ return sieve_opr_number_dump(denv, address, "limit");
+}
+
+/*
+ * Code execution
+ */
+
+static inline bool
+tst_size_get(const struct sieve_runtime_env *renv, sieve_number_t *size)
+{
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ uoff_t psize;
+
+ if (mail_get_physical_size(mail, &psize) < 0)
+ return FALSE;
+
+ *size = psize;
+ return TRUE;
+}
+
+static int
+tst_size_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ sieve_number_t mail_size, limit;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read size limit */
+ if ((ret = sieve_opr_number_read(renv, address, "limit", &limit)) <= 0)
+ return ret;
+
+ /*
+ * Perform test
+ */
+
+ /* Get the size of the message */
+ if (!tst_size_get(renv, &mail_size)) {
+ /* FIXME: improve this error */
+ e_error(renv->event, "failed to assess message size");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /* Perform the test */
+ if (sieve_operation_is(renv->oprtn, tst_size_over_operation)) {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "size :over test");
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING)) {
+ sieve_runtime_trace_descend(renv);
+
+ sieve_runtime_trace(
+ renv, 0, "comparing message size %llu",
+ (unsigned long long)mail_size);
+ sieve_runtime_trace(
+ renv, 0, "with upper limit %llu",
+ (unsigned long long)limit);
+ }
+
+ sieve_interpreter_set_test_result(renv->interp,
+ (mail_size > limit));
+ } else {
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "size :under test");
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING)) {
+ sieve_runtime_trace_descend(renv);
+
+ sieve_runtime_trace(
+ renv, 0, "comparing message size %llu",
+ (unsigned long long)mail_size);
+ sieve_runtime_trace(
+ renv, 0, "with lower limit %llu",
+ (unsigned long long)limit);
+ }
+
+ sieve_interpreter_set_test_result(renv->interp,
+ (mail_size < limit));
+ }
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/lib-sieve/tst-truefalse.c b/pigeonhole/src/lib-sieve/tst-truefalse.c
new file mode 100644
index 0000000..6373ce3
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/tst-truefalse.c
@@ -0,0 +1,104 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-ast.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-interpreter.h"
+
+/*
+ * True/False test command
+ */
+
+static bool tst_false_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *tst,
+ int *const_current, int const_next);
+static bool tst_false_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+ struct sieve_jumplist *jumps, bool jump_true);
+
+const struct sieve_command_def tst_false = {
+ .identifier = "false",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate_const = tst_false_validate_const,
+ .control_generate = tst_false_generate
+};
+
+static bool tst_true_validate_const
+ (struct sieve_validator *valdtr, struct sieve_command *tst,
+ int *const_current, int const_next);
+static bool tst_true_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd,
+ struct sieve_jumplist *jumps, bool jump_true);
+
+const struct sieve_command_def tst_true = {
+ .identifier = "true",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate_const = tst_true_validate_const,
+ .control_generate = tst_true_generate
+};
+
+/*
+ * Code validation
+ */
+
+static bool tst_false_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED, int *const_current,
+ int const_next ATTR_UNUSED)
+{
+ *const_current = 0;
+ return TRUE;
+}
+
+static bool tst_true_validate_const
+(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *tst ATTR_UNUSED, int *const_current,
+ int const_next ATTR_UNUSED)
+{
+ *const_current = 1;
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_false_generate
+(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd ATTR_UNUSED,
+ struct sieve_jumplist *jumps, bool jump_true)
+{
+ if ( !jump_true ) {
+ sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+ sieve_jumplist_add(jumps, sieve_binary_emit_offset(cgenv->sblock, 0));
+ }
+
+ return TRUE;
+}
+
+static bool tst_true_generate
+(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd ATTR_UNUSED,
+ struct sieve_jumplist *jumps, bool jump_true)
+{
+ if ( jump_true ) {
+ sieve_operation_emit(cgenv->sblock, NULL, &sieve_jmp_operation);
+ sieve_jumplist_add(jumps, sieve_binary_emit_offset(cgenv->sblock, 0));
+ }
+
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/lib-sieve/util/Makefile.am b/pigeonhole/src/lib-sieve/util/Makefile.am
new file mode 100644
index 0000000..36cad8a
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/Makefile.am
@@ -0,0 +1,51 @@
+noinst_LTLIBRARIES = libsieve_util.la
+
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS)
+
+libsieve_util_la_SOURCES = \
+ mail-raw.c \
+ edit-mail.c \
+ rfc2822.c
+
+headers = \
+ mail-raw.h \
+ edit-mail.h \
+ rfc2822.h
+
+pkginc_libdir=$(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+ test-edit-mail \
+ test-rfc2822
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+test_deps = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_edit_mail_SOURCES = test-edit-mail.c
+test_edit_mail_LDADD = $(test_libs)
+test_edit_mail_DEPENDENCIES = $(test_deps)
+
+test_rfc2822_SOURCES = test-rfc2822.c
+test_rfc2822_LDADD = $(test_libs)
+test_rfc2822_DEPENDENCIES = $(test_deps)
+
+check: check-am check-test
+check-test: all-am
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
diff --git a/pigeonhole/src/lib-sieve/util/Makefile.in b/pigeonhole/src/lib-sieve/util/Makefile.in
new file mode 100644
index 0000000..a62ac20
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/Makefile.in
@@ -0,0 +1,810 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib-sieve/util
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = test-edit-mail$(EXEEXT) test-rfc2822$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsieve_util_la_LIBADD =
+am_libsieve_util_la_OBJECTS = mail-raw.lo edit-mail.lo rfc2822.lo
+libsieve_util_la_OBJECTS = $(am_libsieve_util_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_test_edit_mail_OBJECTS = test-edit-mail.$(OBJEXT)
+test_edit_mail_OBJECTS = $(am_test_edit_mail_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = libsieve_util.la $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_test_rfc2822_OBJECTS = test-rfc2822.$(OBJEXT)
+test_rfc2822_OBJECTS = $(am_test_rfc2822_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/edit-mail.Plo \
+ ./$(DEPDIR)/mail-raw.Plo ./$(DEPDIR)/rfc2822.Plo \
+ ./$(DEPDIR)/test-edit-mail.Po ./$(DEPDIR)/test-rfc2822.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsieve_util_la_SOURCES) $(test_edit_mail_SOURCES) \
+ $(test_rfc2822_SOURCES)
+DIST_SOURCES = $(libsieve_util_la_SOURCES) $(test_edit_mail_SOURCES) \
+ $(test_rfc2822_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsieve_util.la
+AM_CPPFLAGS = \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE) \
+ -DMODULEDIR=\""$(dovecot_moduledir)"\"
+
+libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS)
+libsieve_util_la_SOURCES = \
+ mail-raw.c \
+ edit-mail.c \
+ rfc2822.c
+
+headers = \
+ mail-raw.h \
+ edit-mail.h \
+ rfc2822.h
+
+pkginc_libdir = $(dovecot_pkgincludedir)/sieve
+pkginc_lib_HEADERS = $(headers)
+test_programs = \
+ test-edit-mail \
+ test-rfc2822
+
+test_libs = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE) \
+ $(LIBDOVECOT)
+
+test_deps = \
+ libsieve_util.la \
+ $(LIBDOVECOT_STORAGE_DEPS) \
+ $(LIBDOVECOT_DEPS)
+
+test_edit_mail_SOURCES = test-edit-mail.c
+test_edit_mail_LDADD = $(test_libs)
+test_edit_mail_DEPENDENCIES = $(test_deps)
+test_rfc2822_SOURCES = test-rfc2822.c
+test_rfc2822_LDADD = $(test_libs)
+test_rfc2822_DEPENDENCIES = $(test_deps)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-sieve/util/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-sieve/util/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsieve_util.la: $(libsieve_util_la_OBJECTS) $(libsieve_util_la_DEPENDENCIES) $(EXTRA_libsieve_util_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsieve_util_la_OBJECTS) $(libsieve_util_la_LIBADD) $(LIBS)
+
+test-edit-mail$(EXEEXT): $(test_edit_mail_OBJECTS) $(test_edit_mail_DEPENDENCIES) $(EXTRA_test_edit_mail_DEPENDENCIES)
+ @rm -f test-edit-mail$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_edit_mail_OBJECTS) $(test_edit_mail_LDADD) $(LIBS)
+
+test-rfc2822$(EXEEXT): $(test_rfc2822_OBJECTS) $(test_rfc2822_DEPENDENCIES) $(EXTRA_test_rfc2822_DEPENDENCIES)
+ @rm -f test-rfc2822$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(test_rfc2822_OBJECTS) $(test_rfc2822_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edit-mail.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-raw.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rfc2822.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-edit-mail.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-rfc2822.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/edit-mail.Plo
+ -rm -f ./$(DEPDIR)/mail-raw.Plo
+ -rm -f ./$(DEPDIR)/rfc2822.Plo
+ -rm -f ./$(DEPDIR)/test-edit-mail.Po
+ -rm -f ./$(DEPDIR)/test-rfc2822.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/edit-mail.Plo
+ -rm -f ./$(DEPDIR)/mail-raw.Plo
+ -rm -f ./$(DEPDIR)/rfc2822.Plo
+ -rm -f ./$(DEPDIR)/test-edit-mail.Po
+ -rm -f ./$(DEPDIR)/test-rfc2822.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pkginc_libHEADERS \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+check: check-am check-test
+check-test: all-am
+ for bin in $(test_programs); do \
+ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+ done
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/lib-sieve/util/edit-mail.c b/pigeonhole/src/lib-sieve/util/edit-mail.c
new file mode 100644
index 0000000..31d941e
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/edit-mail.c
@@ -0,0 +1,2254 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mempool.h"
+#include "llist.h"
+#include "istream-private.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "message-parser.h"
+#include "message-header-encode.h"
+#include "message-header-decode.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "index-mail.h"
+#include "raw-storage.h"
+
+#include "rfc2822.h"
+
+#include "edit-mail.h"
+
+/*
+ * Forward declarations
+ */
+
+struct _header_field_index;
+struct _header_field;
+struct _header_index;
+struct _header;
+
+static struct mail_vfuncs edit_mail_vfuncs;
+
+struct edit_mail_istream;
+struct istream *edit_mail_istream_create(struct edit_mail *edmail);
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header);
+
+/*
+ * Raw storage
+ */
+
+static struct mail_user *edit_mail_user = NULL;
+static unsigned int edit_mail_refcount = 0;
+
+static struct mail_user *edit_mail_raw_storage_get(struct mail_user *mail_user)
+{
+ if (edit_mail_user == NULL) {
+ void **sets =
+ master_service_settings_get_others(master_service);
+
+ edit_mail_user = raw_storage_create_from_set(
+ mail_user->set_info, sets[0]);
+ }
+
+ edit_mail_refcount++;
+
+ return edit_mail_user;
+}
+
+static void edit_mail_raw_storage_drop(void)
+{
+ i_assert(edit_mail_refcount > 0);
+
+ if (--edit_mail_refcount != 0)
+ return;
+
+ mail_user_unref(&edit_mail_user);
+ edit_mail_user = NULL;
+}
+
+/*
+ * Headers
+ */
+
+struct _header_field {
+ struct _header *header;
+
+ unsigned int refcount;
+
+ char *data;
+ size_t size;
+ size_t virtual_size;
+ uoff_t offset;
+ unsigned int lines;
+
+ uoff_t body_offset;
+
+ char *utf8_value;
+};
+
+struct _header_field_index {
+ struct _header_field_index *prev, *next;
+
+ struct _header_field *field;
+ struct _header_index *header;
+};
+
+struct _header {
+ unsigned int refcount;
+
+ char *name;
+};
+
+struct _header_index {
+ struct _header_index *prev, *next;
+
+ struct _header *header;
+
+ struct _header_field_index *first, *last;
+
+ unsigned int count;
+};
+
+static inline struct _header *_header_create(const char *name)
+{
+ struct _header *header;
+
+ header = i_new(struct _header, 1);
+ header->name = i_strdup(name);
+ header->refcount = 1;
+
+ return header;
+}
+
+static inline void _header_ref(struct _header *header)
+{
+ header->refcount++;
+}
+
+static inline void _header_unref(struct _header *header)
+{
+ i_assert(header->refcount > 0);
+ if (--header->refcount != 0)
+ return;
+
+ i_free(header->name);
+ i_free(header);
+}
+
+static inline struct _header_field *_header_field_create(struct _header *header)
+{
+ struct _header_field *hfield;
+
+ hfield = i_new(struct _header_field, 1);
+ hfield->refcount = 1;
+ hfield->header = header;
+ if (header != NULL)
+ _header_ref(header);
+
+ return hfield;
+}
+
+static inline void _header_field_ref(struct _header_field *hfield)
+{
+ hfield->refcount++;
+}
+
+static inline void _header_field_unref(struct _header_field *hfield)
+{
+ i_assert(hfield->refcount > 0);
+ if (--hfield->refcount != 0)
+ return;
+
+ if (hfield->header != NULL)
+ _header_unref(hfield->header);
+
+ if (hfield->data != NULL)
+ i_free(hfield->data);
+ if (hfield->utf8_value != NULL)
+ i_free(hfield->utf8_value);
+ i_free(hfield);
+}
+
+/*
+ * Edit mail object
+ */
+
+struct edit_mail {
+ struct mail_private mail;
+ struct mail_private *wrapped;
+
+ struct edit_mail *parent;
+ unsigned int refcount;
+
+ struct istream *wrapped_stream;
+ struct istream *stream;
+
+ struct _header_index *headers_head, *headers_tail;
+ struct _header_field_index *header_fields_head, *header_fields_tail;
+ struct message_size hdr_size, body_size;
+
+ struct message_size wrapped_hdr_size, wrapped_body_size;
+
+ struct _header_field_index *header_fields_appended;
+ struct message_size appended_hdr_size;
+
+ bool modified:1;
+ bool snapshot_modified:1;
+ bool crlf:1;
+ bool eoh_crlf:1;
+ bool headers_parsed:1;
+ bool destroying_stream:1;
+};
+
+struct edit_mail *edit_mail_wrap(struct mail *mail)
+{
+ struct mail_private *mailp = (struct mail_private *) mail;
+ struct edit_mail *edmail;
+ struct mail_user *raw_mail_user;
+ struct mailbox *raw_box = NULL;
+ struct mailbox_transaction_context *raw_trans;
+ struct message_size hdr_size, body_size;
+ struct istream *wrapped_stream;
+ uoff_t size_diff;
+ pool_t pool;
+
+ if (mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0)
+ return NULL;
+
+ /* Create dummy raw mailbox for our wrapper */
+
+ raw_mail_user = edit_mail_raw_storage_get(mail->box->storage->user);
+
+ if (raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
+ "editor@example.com", &raw_box) < 0) {
+ i_error("edit-mail: failed to open raw box: %s",
+ mailbox_get_last_internal_error(raw_box, NULL));
+ mailbox_free(&raw_box);
+ edit_mail_raw_storage_drop();
+ return NULL;
+ }
+
+ raw_trans = mailbox_transaction_begin(raw_box, 0, __func__);
+
+ /* Create the wrapper mail */
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail = p_new(pool, struct edit_mail, 1);
+ edmail->refcount = 1;
+ edmail->mail.pool = pool;
+
+ edmail->wrapped = mailp;
+ edmail->wrapped_hdr_size = hdr_size;
+ edmail->wrapped_body_size = body_size;
+
+ edmail->wrapped_stream = wrapped_stream;
+ i_stream_ref(edmail->wrapped_stream);
+
+ /* Determine whether we should use CRLF or LF for the physical message
+ */
+ size_diff = ((hdr_size.virtual_size + body_size.virtual_size) -
+ (hdr_size.physical_size + body_size.physical_size));
+ if (size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2)
+ edmail->crlf = edmail->eoh_crlf = TRUE;
+
+ array_create(&edmail->mail.module_contexts, pool, sizeof(void *), 5);
+
+ edmail->mail.v = edit_mail_vfuncs;
+ edmail->mail.mail.seq = 1;
+ edmail->mail.mail.box = raw_box;
+ edmail->mail.mail.transaction = raw_trans;
+ edmail->mail.wanted_fields = mailp->wanted_fields;
+ edmail->mail.wanted_headers = mailp->wanted_headers;
+
+ return edmail;
+}
+
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail)
+{
+ struct _header_field_index *field_idx, *field_idx_new;
+ struct edit_mail *edmail_new;
+ pool_t pool;
+
+ if (!edmail->snapshot_modified)
+ return edmail;
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail_new = p_new(pool, struct edit_mail, 1);
+ edmail_new->refcount = 1;
+ edmail_new->mail.pool = pool;
+
+ edmail_new->wrapped = edmail->wrapped;
+ edmail_new->wrapped_hdr_size = edmail->wrapped_hdr_size;
+ edmail_new->wrapped_body_size = edmail->wrapped_body_size;
+ edmail_new->hdr_size = edmail->hdr_size;
+ edmail_new->body_size = edmail->body_size;
+ edmail_new->appended_hdr_size = edmail->appended_hdr_size;
+
+ edmail_new->wrapped_stream = edmail->wrapped_stream;
+ i_stream_ref(edmail_new->wrapped_stream);
+
+ edmail_new->crlf = edmail->crlf;
+ edmail_new->eoh_crlf = edmail->eoh_crlf;
+
+ array_create(&edmail_new->mail.module_contexts, pool,
+ sizeof(void *), 5);
+
+ edmail_new->mail.v = edit_mail_vfuncs;
+ edmail_new->mail.mail.seq = 1;
+ edmail_new->mail.mail.box = edmail->mail.mail.box;
+ edmail_new->mail.mail.transaction = edmail->mail.mail.transaction;
+ edmail_new->mail.wanted_fields = edmail->mail.wanted_fields;
+ edmail_new->mail.wanted_headers = edmail->mail.wanted_headers;
+
+ edmail_new->stream = NULL;
+
+ if (edmail->modified) {
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ field_idx_new->header = edit_mail_header_clone(
+ edmail_new, field_idx->header->header);
+
+ field_idx_new->field = field_idx->field;
+ _header_field_ref(field_idx_new->field);
+
+ DLLIST2_APPEND(&edmail_new->header_fields_head,
+ &edmail_new->header_fields_tail,
+ field_idx_new);
+
+ field_idx_new->header->count++;
+ if (field_idx->header->first == field_idx)
+ field_idx_new->header->first = field_idx_new;
+ if (field_idx->header->last == field_idx)
+ field_idx_new->header->last = field_idx_new;
+
+ if (field_idx == edmail->header_fields_appended) {
+ edmail_new->header_fields_appended =
+ field_idx_new;
+ }
+
+ field_idx = next;
+ }
+
+ edmail_new->modified = TRUE;
+ }
+
+ edmail_new->headers_parsed = edmail->headers_parsed;
+ edmail_new->parent = edmail;
+
+ return edmail_new;
+}
+
+void edit_mail_reset(struct edit_mail *edmail)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+
+ i_stream_unref(&edmail->stream);
+
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+
+ field_idx = next;
+ }
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ struct _header_index *next = header_idx->next;
+
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+
+ header_idx = next;
+ }
+
+ edmail->modified = FALSE;
+}
+
+void edit_mail_unwrap(struct edit_mail **edmail)
+{
+ struct edit_mail *parent;
+
+ i_assert((*edmail)->refcount > 0);
+ if (--(*edmail)->refcount != 0)
+ return;
+
+ edit_mail_reset(*edmail);
+ i_stream_unref(&(*edmail)->wrapped_stream);
+
+ parent = (*edmail)->parent;
+
+ if (parent == NULL) {
+ mailbox_transaction_rollback(&(*edmail)->mail.mail.transaction);
+ mailbox_free(&(*edmail)->mail.mail.box);
+ edit_mail_raw_storage_drop();
+ }
+
+ pool_unref(&(*edmail)->mail.pool);
+ *edmail = NULL;
+
+ if (parent != NULL)
+ edit_mail_unwrap(&parent);
+}
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail)
+{
+ /* Return wrapped mail when nothing is modified yet */
+ if (!edmail->modified)
+ return &edmail->wrapped->mail;
+
+ return &edmail->mail.mail;
+}
+
+/*
+ * Editing
+ */
+
+static inline void edit_mail_modify(struct edit_mail *edmail)
+{
+ edmail->mail.mail.seq++;
+ edmail->modified = TRUE;
+ edmail->snapshot_modified = TRUE;
+}
+
+/* Header modification */
+
+static inline char *_header_value_unfold(const char *value)
+{
+ string_t *out;
+ unsigned int i;
+
+ for (i = 0; value[i] != '\0'; i++) {
+ if (value[i] == '\r' || value[i] == '\n')
+ break;
+ }
+ if (value[i] == '\0')
+ return i_strdup(value);
+
+ out = t_str_new(i + strlen(value+i) + 10);
+ str_append_data(out, value, i);
+ for (; value[i] != '\0'; i++) {
+ if (value[i] == '\n') {
+ i++;
+ if (value[i] == '\0')
+ break;
+
+ switch (value[i]) {
+ case ' ':
+ str_append_c(out, ' ');
+ break;
+ case '\t':
+ default:
+ str_append_c(out, '\t');
+ }
+ } else {
+ if (value[i] != '\r')
+ str_append_c(out, value[i]);
+ }
+ }
+
+ return i_strndup(str_c(out), str_len(out));
+}
+
+static struct _header_index *
+edit_mail_header_find(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (strcasecmp(header_idx->header->name, field_name) == 0)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ return NULL;
+}
+
+static struct _header_index *
+edit_mail_header_create(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = _header_create(field_name);
+
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ }
+
+ return header_idx;
+}
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (header_idx->header == header)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = header;
+ _header_ref(header);
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+
+ return header_idx;
+}
+
+static struct _header_field_index *
+edit_mail_header_field_create(struct edit_mail *edmail, const char *field_name,
+ const char *value)
+{
+ struct _header_index *header_idx;
+ struct _header *header;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+ unsigned int lines;
+
+ /* Get/create header index item */
+ header_idx = edit_mail_header_create(edmail, field_name);
+ header = header_idx->header;
+
+ /* Create new field index item */
+ field_idx = i_new(struct _header_field_index, 1);
+ field_idx->header = header_idx;
+ field_idx->field = field = _header_field_create(header);
+
+ /* Create header field data (folded if necessary) */
+ T_BEGIN {
+ string_t *enc_value, *data;
+
+ enc_value = t_str_new(strlen(field_name) + strlen(value) + 64);
+ data = t_str_new(strlen(field_name) + strlen(value) + 128);
+
+ message_header_encode(value, enc_value);
+
+ lines = rfc2822_header_append(data, field_name,
+ str_c(enc_value), edmail->crlf,
+ &field->body_offset);
+
+ /* Copy to new field */
+ field->data = i_strndup(str_data(data), str_len(data));
+ field->size = str_len(data);
+ field->virtual_size = (edmail->crlf ?
+ field->size : field->size + lines);
+ field->lines = lines;
+ } T_END;
+
+ /* Record original (utf8) value */
+ field->utf8_value = _header_value_unfold(value);
+
+ return field_idx;
+}
+
+static void
+edit_mail_header_field_delete(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ bool update_index)
+{
+ struct _header_index *header_idx = field_idx->header;
+ struct _header_field *field = field_idx->field;
+
+ i_assert(header_idx != NULL);
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ header_idx->count--;
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ }
+
+ DLLIST2_REMOVE(&edmail->header_fields_head, &edmail->header_fields_tail,
+ field_idx);
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+}
+
+static struct _header_field_index *
+edit_mail_header_field_replace(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ const char *newname, const char *newvalue,
+ bool update_index)
+{
+ struct _header_field_index *field_idx_new;
+ struct _header_index *header_idx = field_idx->header, *header_idx_new;
+ struct _header_field *field = field_idx->field, *field_new;
+
+ i_assert(header_idx != NULL);
+ i_assert(newname != NULL || newvalue != NULL);
+
+ if (newname == NULL)
+ newname = header_idx->header->name;
+ if (newvalue == NULL)
+ newvalue = field_idx->field->utf8_value;
+ field_idx_new = edit_mail_header_field_create(
+ edmail, newname, newvalue);
+ field_new = field_idx_new->field;
+ header_idx_new = field_idx_new->header;
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ edmail->hdr_size.physical_size += field_new->size;
+ edmail->hdr_size.virtual_size += field_new->virtual_size;
+ edmail->hdr_size.lines += field_new->lines;
+
+ /* Replace header field index */
+ field_idx_new->prev = field_idx->prev;
+ field_idx_new->next = field_idx->next;
+ if (field_idx->prev != NULL)
+ field_idx->prev->next = field_idx_new;
+ if (field_idx->next != NULL)
+ field_idx->next->prev = field_idx_new;
+ if (edmail->header_fields_head == field_idx)
+ edmail->header_fields_head = field_idx_new;
+ if (edmail->header_fields_tail == field_idx)
+ edmail->header_fields_tail = field_idx_new;
+
+ if (header_idx_new == header_idx) {
+ if (header_idx->first == field_idx)
+ header_idx->first = field_idx_new;
+ if (header_idx->last == field_idx)
+ header_idx->last = field_idx_new;
+ } else {
+ header_idx->count--;
+ header_idx_new->count++;
+
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ if (header_idx_new->count > 0) {
+ struct _header_field_index *hfield;
+
+ hfield = edmail->header_fields_head;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx_new->first = hfield;
+
+ hfield = edmail->header_fields_tail;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx_new->last = hfield;
+ }
+ }
+ }
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+ return field_idx_new;
+}
+
+static inline char *
+_header_decode(const unsigned char *hdr_data, size_t hdr_data_len)
+{
+ string_t *str = t_str_new(512);
+
+ /* hdr_data is already unfolded */
+
+ /* Decode MIME encoded-words. */
+ message_header_decode_utf8((const unsigned char *)hdr_data,
+ hdr_data_len, str, NULL);
+ return i_strdup(str_c(str));
+}
+
+static int edit_mail_headers_parse(struct edit_mail *edmail)
+{
+ struct message_header_parser_ctx *hparser;
+ enum message_header_parser_flags hparser_flags =
+ MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+ struct message_header_line *hdr;
+ struct _header_index *header_idx;
+ struct _header_field_index *head = NULL, *tail = NULL, *current;
+ string_t *hdr_data;
+ uoff_t offset = 0, body_offset = 0, vsize_diff = 0;
+ unsigned int lines = 0;
+ int ret;
+
+ if (edmail->headers_parsed)
+ return 1;
+
+ i_stream_seek(edmail->wrapped_stream, 0);
+ hparser = message_parse_header_init(edmail->wrapped_stream, NULL,
+ hparser_flags);
+
+ T_BEGIN {
+ hdr_data = t_str_new(1024);
+ while ((ret = message_parse_header_next(hparser, &hdr)) > 0) {
+ struct _header_field_index *field_idx_new;
+ struct _header_field *field;
+
+ if (hdr->eoh) {
+ /* Record whether header ends in CRLF or LF */
+ edmail->eoh_crlf = hdr->crlf_newline;
+ }
+
+ if (hdr == NULL || hdr->eoh)
+ break;
+
+ /* Skip bad headers */
+ if (hdr->name_len == 0)
+ continue;
+ /* We deny the existence of any `Content-Length:'
+ header. This header is non-standard and it can wreak
+ havok when the message is modified.
+ */
+ if (strcasecmp(hdr->name, "Content-Length" ) == 0)
+ continue;
+
+ if (hdr->continued) {
+ /* Continued line of folded header */
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ } else {
+ /* First line of header */
+ offset = hdr->name_offset;
+ body_offset = hdr->name_len + hdr->middle_len;
+ str_truncate(hdr_data, 0);
+ buffer_append(hdr_data, hdr->name,
+ hdr->name_len);
+ buffer_append(hdr_data, hdr->middle,
+ hdr->middle_len);
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ lines = 0;
+ vsize_diff = 0;
+ }
+
+ if (!hdr->no_newline) {
+ lines++;
+
+ if (hdr->crlf_newline) {
+ buffer_append(hdr_data, "\r\n", 2);
+ } else {
+ buffer_append(hdr_data, "\n", 1);
+ vsize_diff++;
+ }
+ }
+
+ if (hdr->continues) {
+ hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ /* Create new header field index entry */
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ header_idx = edit_mail_header_create(edmail, hdr->name);
+ header_idx->count++;
+ field_idx_new->header = header_idx;
+ field_idx_new->field = field =
+ _header_field_create(header_idx->header);
+
+ i_assert(body_offset > 0);
+ field->body_offset = body_offset;
+
+ field->utf8_value = _header_decode(hdr->full_value,
+ hdr->full_value_len);
+
+ field->size = str_len(hdr_data);
+ field->virtual_size = field->size + vsize_diff;
+ field->data = i_strndup(str_data(hdr_data),
+ field->size);
+ field->offset = offset;
+ field->lines = lines;
+
+ DLLIST2_APPEND(&head, &tail, field_idx_new);
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += lines;
+ }
+ } T_END;
+
+ message_parse_header_deinit(&hparser);
+
+ /* Blocking i/o required */
+ i_assert(ret != 0);
+
+ if (ret < 0 && edmail->wrapped_stream->stream_errno != 0) {
+ /* Error; clean up */
+ i_error("read(%s) failed: %s",
+ i_stream_get_name(edmail->wrapped_stream),
+ i_stream_get_error(edmail->wrapped_stream));
+ current = head;
+ while (current != NULL) {
+ struct _header_field_index *next = current->next;
+
+ _header_field_unref(current->field);
+ i_free(current);
+
+ current = next;
+ }
+
+ return ret;
+ }
+
+ /* Insert header field index items in main list */
+ if (head != NULL && tail != NULL) {
+ if (edmail->header_fields_appended != NULL) {
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended) {
+ edmail->header_fields_appended->prev->next = head;
+ head->prev = edmail->header_fields_appended->prev;
+ } else {
+ edmail->header_fields_head = head;
+ }
+
+ tail->next = edmail->header_fields_appended;
+ edmail->header_fields_appended->prev = tail;
+ } else if (edmail->header_fields_tail != NULL) {
+ edmail->header_fields_tail->next = head;
+ head->prev = edmail->header_fields_tail;
+ edmail->header_fields_tail = tail;
+ } else {
+ edmail->header_fields_head = head;
+ edmail->header_fields_tail = tail;
+ }
+ }
+
+ /* Rebuild header index */
+ current = edmail->header_fields_head;
+ while (current != NULL) {
+ if (current->header->first == NULL)
+ current->header->first = current;
+ current->header->last = current;
+
+ current = current->next;
+ }
+
+ /* Clear appended headers */
+ edmail->header_fields_appended = NULL;
+ edmail->appended_hdr_size.physical_size = 0;
+ edmail->appended_hdr_size.virtual_size = 0;
+ edmail->appended_hdr_size.lines = 0;
+
+ /* Do not parse headers again */
+ edmail->headers_parsed = TRUE;
+
+ return 1;
+}
+
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+ const char *value, bool last)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+
+ edit_mail_modify(edmail);
+
+ field_idx = edit_mail_header_field_create(edmail, field_name, value);
+ header_idx = field_idx->header;
+ field = field_idx->field;
+
+ /* Add it to the header field index */
+ if (last) {
+ DLLIST2_APPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->last = field_idx;
+ if (header_idx->first == NULL)
+ header_idx->first = field_idx;
+
+ if (!edmail->headers_parsed) {
+ if (edmail->header_fields_appended == NULL) {
+ /* Record beginning of appended headers */
+ edmail->header_fields_appended = field_idx;
+ }
+
+ edmail->appended_hdr_size.physical_size += field->size;
+ edmail->appended_hdr_size.virtual_size += field->virtual_size;
+ edmail->appended_hdr_size.lines += field->lines;
+ }
+ } else {
+ DLLIST2_PREPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->first = field_idx;
+ if (header_idx->last == NULL)
+ header_idx->last = field_idx;
+ }
+
+ header_idx->count++;
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += field->lines;
+}
+
+int edit_mail_header_delete(struct edit_mail *edmail, const char *field_name,
+ int index)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and remove those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ edit_mail_header_field_delete(
+ edmail, field_idx, FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ if (index == 0 || header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+int edit_mail_header_replace(struct edit_mail *edmail,
+ const char *field_name, int index,
+ const char *newname, const char *newvalue)
+{
+ struct _header_index *header_idx, *header_idx_new;
+ struct _header_field_index *field_idx, *field_idx_new;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and replace those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ field_idx_new = NULL;
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ field_idx_new = edit_mail_header_field_replace(
+ edmail, field_idx, newname, newvalue,
+ FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ /* Update old header index */
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ /* Update new header index */
+ if (field_idx_new != NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ header_idx_new = field_idx_new->header;
+ while (current != NULL) {
+ if (current->header == header_idx_new) {
+ if (header_idx_new->first == NULL)
+ header_idx_new->first = current;
+ header_idx_new->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+struct edit_mail_header_iter
+{
+ struct edit_mail *mail;
+ struct _header_index *header;
+ struct _header_field_index *current;
+
+ bool reverse:1;
+};
+
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+ const char *field_name, bool reverse,
+ struct edit_mail_header_iter **edhiter_r)
+{
+ struct edit_mail_header_iter *edhiter;
+ struct _header_index *header_idx = NULL;
+ struct _header_field_index *current = NULL;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0) {
+ /* Failure */
+ return -1;
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+
+ if (field_name != NULL && header_idx == NULL) {
+ current = NULL;
+ } else if (!reverse) {
+ current = (header_idx != NULL ?
+ header_idx->first : edmail->header_fields_head);
+ } else {
+ current = (header_idx != NULL ?
+ header_idx->last : edmail->header_fields_tail);
+ if (current->header == NULL)
+ current = current->prev;
+ }
+
+ if (current == NULL)
+ return 0;
+
+ edhiter = i_new(struct edit_mail_header_iter, 1);
+ edhiter->mail = edmail;
+ edhiter->header = header_idx;
+ edhiter->reverse = reverse;
+ edhiter->current = current;
+
+ *edhiter_r = edhiter;
+ return 1;
+}
+
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter)
+{
+ i_free(*edhiter);
+ *edhiter = NULL;
+}
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+ const char **value_r)
+{
+ const char *raw;
+ int i;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ raw = edhiter->current->field->utf8_value;
+ for (i = strlen(raw)-1; i >= 0; i--) {
+ if (raw[i] != ' ' && raw[i] != '\t')
+ break;
+ }
+
+ *value_r = t_strndup(raw, i+1);
+}
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter)
+{
+ if (edhiter->current == NULL)
+ return FALSE;
+
+ do {
+ edhiter->current = (!edhiter->reverse ?
+ edhiter->current->next :
+ edhiter->current->prev );
+ } while (edhiter->current != NULL && edhiter->current->header != NULL &&
+ edhiter->header != NULL &&
+ edhiter->current->header != edhiter->header);
+
+ return (edhiter->current != NULL && edhiter->current->header != NULL);
+}
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_delete(edhiter->mail, field_idx, TRUE);
+
+ return next;
+}
+
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+ const char *newname,
+ const char *newvalue)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_replace(edhiter->mail, field_idx,
+ newname, newvalue, TRUE);
+
+ return next;
+}
+
+/* Body modification */
+
+// FIXME: implement
+
+/*
+ * Mail API
+ */
+
+static void edit_mail_close(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.close(&edmail->wrapped->mail);
+}
+
+static void edit_mail_free(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.free(&edmail->wrapped->mail);
+
+ edit_mail_unwrap(&edmail);
+}
+
+static void
+edit_mail_set_seq(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
+ bool saving ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_seq() not implemented");
+}
+
+static bool ATTR_NORETURN
+edit_mail_set_uid(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_uid() not implemented");
+}
+
+static void edit_mail_set_uid_cache_updates(struct mail *mail, bool set)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_uid_cache_updates(&edmail->wrapped->mail, set);
+}
+
+static void
+edit_mail_add_temp_wanted_fields(
+ struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED)
+{
+ /* Nothing */
+}
+
+static enum mail_flags edit_mail_get_flags(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_flags(&edmail->wrapped->mail);
+}
+
+static const char *const *edit_mail_get_keywords(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keywords(&edmail->wrapped->mail);
+}
+
+static const ARRAY_TYPE(keyword_indexes) *
+edit_mail_get_keyword_indexes(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keyword_indexes(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_modseq(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_pvt_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_pvt_modseq(&edmail->wrapped->mail);
+}
+
+static int edit_mail_get_parts(struct mail *mail, struct message_part **parts_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_parts(&edmail->wrapped->mail, parts_r);
+}
+
+static int
+edit_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_date(&edmail->wrapped->mail,
+ date_r, timezone_r);
+}
+
+static int edit_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail,
+ date_r);
+}
+
+static int edit_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_save_date(&edmail->wrapped->mail, date_r);
+}
+
+static int edit_mail_get_virtual_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.virtual_size +
+ edmail->wrapped_body_size.virtual_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = edmail->wrapped_body_size.virtual_size + 2;
+ }
+
+ *size_r += (edmail->hdr_size.virtual_size +
+ edmail->body_size.virtual_size);
+ return 0;
+}
+
+static int edit_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *size_r = 0;
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.physical_size +
+ edmail->wrapped_body_size.physical_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ *size_r += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+static int
+edit_mail_get_first_header(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field *field;
+ int ret;
+
+ /* Check whether mail headers were modified at all */
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ /* Try to find modified header */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ *value_r = NULL;
+ return 0;
+ }
+
+ /* Get the first occurrence */
+ if (edmail->header_fields_appended == NULL) {
+ /* There are no appended headers, so first is found directly */
+ field = header_idx->first->field;
+ } else {
+ struct _header_field_index *field_idx;
+
+ /* Scan prepended headers */
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ if (field_idx->header == header_idx)
+ break;
+
+ if (field_idx == edmail->header_fields_appended) {
+ field_idx = NULL;
+ break;
+ }
+ field_idx = field_idx->next;
+ }
+
+ if (field_idx == NULL) {
+ /* Check original message */
+ ret = edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ if (ret != 0)
+ return ret;
+
+ /* Use first (apparently appended) header */
+ field = header_idx->first->field;
+ } else {
+ field = field_idx->field;
+ }
+ }
+
+ if (decode_to_utf8)
+ *value_r = field->utf8_value;
+ else
+ *value_r = (const char *)(field->data + field->body_offset);
+ return 1;
+}
+
+static int
+edit_mail_get_headers(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char *const **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ const char *const *headers;
+ ARRAY(const char *) header_values;
+
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ p_array_init(&header_values, edmail->mail.pool, 1);
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 0;
+ }
+
+ /* Merge */
+
+ /* Read original headers too if message headers are not parsed */
+ headers = NULL;
+ if (!edmail->headers_parsed &&
+ edmail->wrapped->v.get_headers(&edmail->wrapped->mail, field_name,
+ decode_to_utf8, &headers) < 0)
+ return -1;
+
+ /* Fill result array */
+ p_array_init(&header_values, edmail->mail.pool, 32);
+ field_idx = header_idx->first;
+ while (field_idx != NULL) {
+ /* If current field is the first appended one, we need to add
+ original headers first.
+ */
+ if (field_idx == edmail->header_fields_appended &&
+ headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ /* Add modified header to the list */
+ if (field_idx->field->header == header_idx->header) {
+ struct _header_field *field = field_idx->field;
+
+ const char *value;
+ if (decode_to_utf8)
+ value = field->utf8_value;
+ else {
+ value = (const char *)(field->data +
+ field->body_offset);
+ }
+
+ array_append(&header_values, &value, 1);
+
+ if (field_idx == header_idx->last)
+ break;
+ }
+
+ field_idx = field_idx->next;
+ }
+
+ /* Add original headers if necessary */
+ if (headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 1;
+}
+
+static int ATTR_NORETURN
+edit_mail_get_header_stream(
+ struct mail *mail ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED,
+ struct istream **stream_r ATTR_UNUSED)
+{
+ // FIXME: implement!
+ i_panic("edit_mail_get_header_stream() not implemented");
+}
+
+static int
+edit_mail_get_stream(struct mail *mail, bool get_body ATTR_UNUSED,
+ struct message_size *hdr_size,
+ struct message_size *body_size, struct istream **stream_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->stream == NULL)
+ edmail->stream = edit_mail_istream_create(edmail);
+
+ if (hdr_size != NULL) {
+ *hdr_size = edmail->wrapped_hdr_size;
+ hdr_size->physical_size += edmail->hdr_size.physical_size;
+ hdr_size->virtual_size += edmail->hdr_size.virtual_size;
+ hdr_size->lines += edmail->hdr_size.lines;
+ }
+
+ if (body_size != NULL)
+ *body_size = edmail->wrapped_body_size;
+
+ *stream_r = edmail->stream;
+ i_stream_seek(edmail->stream, 0);
+
+ return 0;
+}
+
+static int
+edit_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->modified) {
+ /* Block certain fields when modified */
+
+ switch (field) {
+ case MAIL_FETCH_GUID:
+ /* This is in essence a new message */
+ *value_r = "";
+ return 0;
+ case MAIL_FETCH_STORAGE_ID:
+ /* Prevent hardlink copying */
+ *value_r = "";
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return edmail->wrapped->v.get_special(&edmail->wrapped->mail,
+ field, value_r);
+}
+
+static int
+edit_mail_get_backend_mail(struct mail *mail, struct mail **real_mail_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *real_mail_r = edit_mail_get_mail(edmail);
+ return 0;
+}
+
+static void
+edit_mail_update_flags(struct mail *mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_flags(&edmail->wrapped->mail,
+ modify_type, flags);
+}
+
+static void
+edit_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_keywords(&edmail->wrapped->mail,
+ modify_type, keywords);
+}
+
+static void edit_mail_update_modseq(struct mail *mail, uint64_t min_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_modseq(&edmail->wrapped->mail, min_modseq);
+}
+
+static void
+edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail,
+ min_pvt_modseq);
+}
+
+static void edit_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->wrapped->v.update_pop3_uidl != NULL) {
+ edmail->wrapped->v.update_pop3_uidl(
+ &edmail->wrapped->mail, uidl);
+ }
+}
+
+static void edit_mail_expunge(struct mail *mail ATTR_UNUSED)
+{
+ /* NOOP */
+}
+
+static void
+edit_mail_set_cache_corrupted(struct mail *mail, enum mail_fetch_field field,
+ const char *reason)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_cache_corrupted(&edmail->wrapped->mail,
+ field, reason);
+}
+
+static struct mail_vfuncs edit_mail_vfuncs = {
+ edit_mail_close,
+ edit_mail_free,
+ edit_mail_set_seq,
+ edit_mail_set_uid,
+ edit_mail_set_uid_cache_updates,
+ NULL,
+ NULL,
+ edit_mail_add_temp_wanted_fields,
+ edit_mail_get_flags,
+ edit_mail_get_keywords,
+ edit_mail_get_keyword_indexes,
+ edit_mail_get_modseq,
+ edit_mail_get_pvt_modseq,
+ edit_mail_get_parts,
+ edit_mail_get_date,
+ edit_mail_get_received_date,
+ edit_mail_get_save_date,
+ edit_mail_get_virtual_size,
+ edit_mail_get_physical_size,
+ edit_mail_get_first_header,
+ edit_mail_get_headers,
+ edit_mail_get_header_stream,
+ edit_mail_get_stream,
+ index_mail_get_binary_stream,
+ edit_mail_get_special,
+ edit_mail_get_backend_mail,
+ edit_mail_update_flags,
+ edit_mail_update_keywords,
+ edit_mail_update_modseq,
+ edit_mail_update_pvt_modseq,
+ edit_mail_update_pop3_uidl,
+ edit_mail_expunge,
+ edit_mail_set_cache_corrupted,
+ NULL,
+};
+
+/*
+ * Edit Mail Stream
+ */
+
+struct edit_mail_istream {
+ struct istream_private istream;
+ pool_t pool;
+
+ struct edit_mail *mail;
+
+ struct _header_field_index *cur_header;
+ uoff_t cur_header_v_offset;
+
+ bool parent_buffer:1;
+ bool header_read:1;
+ bool eof:1;
+};
+
+static void edit_mail_istream_destroy(struct iostream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+
+ i_stream_unref(&edstream->istream.parent);
+ i_stream_free_buffer(&edstream->istream);
+ pool_unref(&edstream->pool);
+}
+
+static ssize_t
+merge_from_parent(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
+ uoff_t parent_end_v_offset, uoff_t copy_v_offset)
+{
+ struct istream_private *stream = &edstream->istream;
+ uoff_t v_offset, append_v_offset;
+ const unsigned char *data;
+ size_t pos, cur_pos, parent_bytes_left;
+ bool parent_buffer = edstream->parent_buffer;
+ ssize_t ret;
+
+ i_assert(parent_v_offset <= parent_end_v_offset);
+ edstream->parent_buffer = FALSE;
+
+ v_offset = stream->istream.v_offset;
+ if (v_offset >= copy_v_offset) {
+ i_assert((v_offset - copy_v_offset) <= parent_end_v_offset);
+ if ((v_offset - copy_v_offset) == parent_end_v_offset) {
+ /* Parent data is all read */
+ return 0;
+ }
+ }
+
+ /* Determine where we are appending more data to the stream */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (v_offset >= copy_v_offset) {
+ /* Parent buffer used */
+ cur_pos = (stream->pos - stream->skip);
+ parent_v_offset += (v_offset - copy_v_offset);
+ } else {
+ cur_pos = 0;
+ i_assert(append_v_offset >= copy_v_offset);
+ parent_v_offset += (append_v_offset - copy_v_offset);
+ }
+
+ /* Seek parent to required position */
+ i_stream_seek(stream->parent, parent_v_offset);
+
+ /* Read from parent */
+ data = i_stream_get_data(stream->parent, &pos);
+ if (pos > cur_pos)
+ ret = 0;
+ else do {
+ /* Use normal read here, since parent data can be returned
+ directly to caller. */
+ ret = i_stream_read(stream->parent);
+
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ stream->istream.eof = stream->parent->eof;
+ edstream->eof = stream->parent->eof;
+ data = i_stream_get_data(stream->parent, &pos);
+ /* Check again, in case the parent stream had been seeked
+ backwards and the previous read() didn't get us far
+ enough. */
+ } while (pos <= cur_pos && ret > 0);
+
+ /* Don't read beyond parent end offset */
+ if (parent_end_v_offset != (uoff_t)-1) {
+ parent_bytes_left = (size_t)(parent_end_v_offset -
+ parent_v_offset);
+ if (pos >= parent_bytes_left) {
+ pos = parent_bytes_left;
+ }
+ }
+
+ if (v_offset < copy_v_offset || ret == -2 ||
+ (parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
+ /* Merging with our local buffer; copying data from parent */
+ if (pos > 0) {
+ size_t avail;
+
+ if (parent_buffer) {
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+ if (!i_stream_try_alloc(stream, pos, &avail))
+ return -2;
+ pos = (pos > avail ? avail : pos);
+
+ memcpy(stream->w_buffer + stream->pos, data, pos);
+ stream->pos += pos;
+ stream->buffer = stream->w_buffer;
+
+ if (cur_pos >= pos)
+ ret = 0;
+ else
+ ret = (ssize_t)(pos - cur_pos);
+ } else {
+ ret = (ret == 0 ? 0 : -1);
+ }
+ } else {
+ /* Just passing buffers from parent; no copying */
+ ret = (pos > cur_pos ?
+ (ssize_t)(pos - cur_pos) : (ret == 0 ? 0 : -1));
+ stream->buffer = data;
+ stream->pos = pos;
+ stream->skip = 0;
+ edstream->parent_buffer = TRUE;
+ }
+
+ i_assert(ret != -1 || stream->istream.eof ||
+ stream->istream.stream_errno != 0);
+ return ret;
+}
+
+static ssize_t merge_modified_headers(struct edit_mail_istream *edstream)
+{
+ struct istream_private *stream = &edstream->istream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset = stream->istream.v_offset, append_v_offset;
+ size_t appended, written, avail, size;
+
+ if (edstream->cur_header == NULL) {
+ /* No (more) headers */
+ return 0;
+ }
+
+ /* Caller must already have committed remaining parent data to
+ our stream buffer. */
+ i_assert(!edstream->parent_buffer);
+
+ /* Add modified headers to buffer */
+ written = 0;
+ while (edstream->cur_header != NULL) {
+ size_t wsize;
+
+ /* Determine what part of the header was already buffered */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+ i_assert(append_v_offset >= edstream->cur_header_v_offset);
+ if (append_v_offset >= edstream->cur_header_v_offset)
+ appended = (size_t)(append_v_offset -
+ edstream->cur_header_v_offset);
+ else
+ appended = 0;
+ i_assert(appended <= edstream->cur_header->field->size);
+
+ /* Determine how much we want to write */
+ size = edstream->cur_header->field->size - appended;
+ if (size > 0) {
+ /* Determine how much we can write */
+ if (!i_stream_try_alloc(stream, size, &avail)) {
+ if (written == 0)
+ return -2;
+ break;
+ }
+ wsize = (size >= avail ? avail : size);
+
+ /* Write (part of) the header to buffer */
+ memcpy(stream->w_buffer + stream->pos,
+ edstream->cur_header->field->data + appended,
+ wsize);
+ stream->pos += wsize;
+ stream->buffer = stream->w_buffer;
+ written += wsize;
+
+ if (wsize < size) {
+ /* Could not write whole header; finish here */
+ break;
+ }
+ }
+
+ /* Skip to next header */
+ edstream->cur_header_v_offset +=
+ edstream->cur_header->field->size;
+ edstream->cur_header = edstream->cur_header->next;
+
+ /* Stop at end of prepended headers if original header is left
+ unparsed */
+ if (!edmail->headers_parsed &&
+ edstream->cur_header == edmail->header_fields_appended)
+ edstream->cur_header = NULL;
+ }
+
+ if (edstream->cur_header == NULL) {
+ /* Clear offset too, just to be tidy */
+ edstream->cur_header_v_offset = 0;
+ }
+
+ i_assert(written > 0);
+ return (ssize_t)written;
+}
+
+static ssize_t edit_mail_istream_read(struct istream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset, append_v_offset;
+ uoff_t parent_v_offset, parent_end_v_offset, copy_v_offset;
+ uoff_t prep_hdr_size, hdr_size;
+ ssize_t ret = 0;
+
+ if (edstream->eof) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (edstream->parent_buffer && stream->skip == stream->pos) {
+ edstream->parent_buffer = FALSE;
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+
+ /* Merge prepended headers */
+ if (!edstream->parent_buffer) {
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+ v_offset = stream->istream.v_offset;
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (!edmail->headers_parsed && edmail->header_fields_appended != NULL &&
+ !edstream->header_read) {
+ /* Output headers from original stream */
+
+ /* Size of the prepended header */
+ i_assert(edmail->hdr_size.physical_size >=
+ edmail->appended_hdr_size.physical_size);
+ prep_hdr_size = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+
+ /* Calculate offset of header end or appended header. Any final
+ CR is dealt with later.
+ */
+ hdr_size = (prep_hdr_size +
+ edmail->wrapped_hdr_size.physical_size);
+ i_assert(hdr_size > 0);
+ if (append_v_offset <= (hdr_size - 1) &&
+ edmail->wrapped_hdr_size.physical_size > 0) {
+ parent_v_offset = stream->parent_start_offset;
+ parent_end_v_offset =
+ (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size - 1);
+ copy_v_offset = prep_hdr_size;
+
+ ret = merge_from_parent(edstream, parent_v_offset,
+ parent_end_v_offset,
+ copy_v_offset);
+ if (ret < 0)
+ return ret;
+ append_v_offset = (v_offset +
+ (stream->pos - stream->skip));
+ i_assert(append_v_offset <= hdr_size - 1);
+
+ if (append_v_offset == hdr_size - 1) {
+ /* Strip final CR too when it is present */
+ if (stream->buffer != NULL &&
+ stream->buffer[stream->pos-1] == '\r') {
+ stream->pos--;
+ append_v_offset--;
+ ret--;
+ }
+
+ i_assert(ret >= 0);
+ edstream->cur_header =
+ edmail->header_fields_appended;
+ edstream->cur_header_v_offset = append_v_offset;
+ if (!edstream->parent_buffer)
+ edstream->header_read = TRUE;
+ }
+
+ if (ret != 0)
+ return ret;
+ } else {
+ edstream->header_read = TRUE;
+ }
+
+ /* Merge appended headers */
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Header does not come from original mail at all */
+ if (edmail->headers_parsed) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = edmail->hdr_size.physical_size;
+ /* Header comes partially from original mail and headers are added
+ between header and body. */
+ } else if (edmail->header_fields_appended != NULL) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ /* Header comes partially from original mail, but headers are only
+ prepended. */
+ } else {
+ parent_v_offset = stream->parent_start_offset;
+ copy_v_offset = edmail->hdr_size.physical_size;
+ }
+
+ return merge_from_parent(edstream, parent_v_offset, (uoff_t)-1,
+ copy_v_offset);
+}
+
+static void
+stream_reset_to(struct edit_mail_istream *edstream, uoff_t v_offset)
+{
+ edstream->istream.istream.v_offset = v_offset;
+ edstream->istream.skip = 0;
+ edstream->istream.pos = 0;
+ edstream->istream.buffer = NULL;
+ edstream->parent_buffer = FALSE;
+ edstream->eof = FALSE;
+ i_stream_seek(edstream->istream.parent, 0);
+}
+
+static void
+edit_mail_istream_seek(struct istream_private *stream, uoff_t v_offset,
+ bool mark ATTR_UNUSED)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct _header_field_index *cur_header;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t offset;
+
+ edstream->header_read = FALSE;
+ edstream->cur_header = NULL;
+ edstream->cur_header_v_offset = 0;
+
+ /* The beginning */
+ if (v_offset == 0) {
+ stream_reset_to(edstream, 0);
+
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+ return;
+ }
+
+ /* Inside (prepended) headers */
+ if (edmail->headers_parsed) {
+ offset = edmail->hdr_size.physical_size;
+ } else {
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+ }
+
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ /* Find the header */
+ cur_header = edmail->header_fields_head;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+ edstream->cur_header_v_offset = 0;
+ offset = cur_header->field->size;
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+
+ if (!edmail->headers_parsed) {
+ /* Inside original header */
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+ return;
+ }
+
+ edstream->header_read = TRUE;
+
+ /* Inside appended header */
+ offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ offset -= edmail->appended_hdr_size.physical_size;
+
+ cur_header = edmail->header_fields_appended;
+ i_assert(cur_header != NULL);
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+ }
+
+ stream_reset_to(edstream, v_offset);
+ edstream->cur_header = NULL;
+}
+
+static void ATTR_NORETURN
+edit_mail_istream_sync(struct istream_private *stream ATTR_UNUSED)
+{
+ i_panic("edit-mail istream sync() not implemented");
+}
+
+static int
+edit_mail_istream_stat(struct istream_private *stream, bool exact)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ const struct stat *st;
+
+ /* Stat the original stream */
+ if (i_stream_stat(stream->parent, exact, &st) < 0)
+ return -1;
+
+ stream->statbuf = *st;
+ if (st->st_size == -1 || !exact)
+ return 0;
+
+ if (!edmail->headers_parsed) {
+ if (!edmail->modified)
+ return 0;
+ } else {
+ stream->statbuf.st_size =
+ (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ stream->statbuf.st_size += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+struct istream *edit_mail_istream_create(struct edit_mail *edmail)
+{
+ struct edit_mail_istream *edstream;
+ struct istream *wrapped = edmail->wrapped_stream;
+
+ edstream = i_new(struct edit_mail_istream, 1);
+ edstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+ "edit mail stream", 4096);
+ edstream->mail = edmail;
+
+ edstream->istream.max_buffer_size =
+ wrapped->real_stream->max_buffer_size;
+
+ edstream->istream.iostream.destroy = edit_mail_istream_destroy;
+ edstream->istream.read = edit_mail_istream_read;
+ edstream->istream.seek = edit_mail_istream_seek;
+ edstream->istream.sync = edit_mail_istream_sync;
+ edstream->istream.stat = edit_mail_istream_stat;
+
+ edstream->istream.istream.readable_fd = FALSE;
+ edstream->istream.istream.blocking = wrapped->blocking;
+ edstream->istream.istream.seekable = wrapped->seekable;
+
+ if (edmail->header_fields_head != edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+
+ i_stream_seek(wrapped, 0);
+
+ return i_stream_create(&edstream->istream, wrapped, -1, 0);
+}
diff --git a/pigeonhole/src/lib-sieve/util/edit-mail.h b/pigeonhole/src/lib-sieve/util/edit-mail.h
new file mode 100644
index 0000000..14d2eaa
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/edit-mail.h
@@ -0,0 +1,47 @@
+#ifndef EDIT_MAIL_H
+#define EDIT_MAIL_H
+
+struct edit_mail;
+
+struct edit_mail *edit_mail_wrap(struct mail *mail);
+void edit_mail_unwrap(struct edit_mail **edmail);
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail);
+
+void edit_mail_reset(struct edit_mail *edmail);
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail);
+
+/*
+ * Header modification
+ */
+
+/* Simple API */
+
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+ const char *value, bool last);
+int edit_mail_header_delete(struct edit_mail *edmail,
+ const char *field_name, int index);
+int edit_mail_header_replace(struct edit_mail *edmail,
+ const char *field_name, int index,
+ const char *newname, const char *newvalue);
+
+/* Iterator */
+
+struct edit_mail_header_iter;
+
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+ const char *field_name, bool reverse,
+ struct edit_mail_header_iter **edhiter_r);
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter);
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+ const char **value_r);
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter);
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter);
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+ const char *newname,
+ const char *newvalue);
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/mail-raw.c b/pigeonhole/src/lib-sieve/util/mail-raw.c
new file mode 100644
index 0000000..b357fe1
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/mail-raw.c
@@ -0,0 +1,247 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "istream.h"
+#include "istream-seekable.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "safe-mkstemp.h"
+#include "path-util.h"
+#include "message-address.h"
+#include "mbox-from.h"
+#include "raw-storage.h"
+#include "mail-namespace.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "settings-parser.h"
+#include "mail-raw.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_ENVELOPE_SENDER "MAILER-DAEMON"
+
+/* After buffer grows larger than this, create a temporary file to /tmp
+ where to read the mail. */
+#define MAIL_MAX_MEMORY_BUFFER (1024*128)
+
+static const char *wanted_headers[] = {
+ "From", "Message-ID", "Subject", "Return-Path",
+ NULL
+};
+
+/*
+ * Global data
+ */
+
+struct mail_raw_user {
+ struct mail_namespace *ns;
+ struct mail_user *mail_user;
+};
+
+/*
+ * Raw mail implementation
+ */
+
+static int seekable_fd_callback
+(const char **path_r, void *context)
+{
+ struct mail_user *ruser = (struct mail_user *)context;
+ string_t *path;
+ int fd;
+
+ path = t_str_new(128);
+ mail_user_set_get_temp_prefix(path, ruser->set);
+ fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+
+ /* we just want the fd, unlink it */
+ if (i_unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ *path_r = str_c(path);
+ return fd;
+}
+
+static struct istream *mail_raw_create_stream
+(struct mail_user *ruser, int fd, time_t *mtime_r, const char **sender)
+{
+ struct istream *input, *input2, *input_list[2];
+ const unsigned char *data;
+ size_t i, size;
+ int ret, tz;
+ char *env_sender = NULL;
+
+ *mtime_r = (time_t)-1;
+ fd_set_nonblock(fd, FALSE);
+
+ input = i_stream_create_fd(fd, 4096);
+ input->blocking = TRUE;
+ /* If input begins with a From-line, drop it */
+ ret = i_stream_read_bytes(input, &data, &size, 5);
+ if (ret > 0 && memcmp(data, "From ", 5) == 0) {
+ /* skip until the first LF */
+ i_stream_skip(input, 5);
+ while ( i_stream_read_more(input, &data, &size) > 0 ) {
+ for (i = 0; i < size; i++) {
+ if (data[i] == '\n')
+ break;
+ }
+ if (i != size) {
+ (void)mbox_from_parse(data, i, mtime_r, &tz, &env_sender);
+ i_stream_skip(input, i + 1);
+ break;
+ }
+ i_stream_skip(input, size);
+ }
+ }
+
+ if (env_sender != NULL && sender != NULL) {
+ *sender = t_strdup(env_sender);
+ }
+ i_free(env_sender);
+
+ if (input->v_offset == 0) {
+ input2 = input;
+ i_stream_ref(input2);
+ } else {
+ input2 = i_stream_create_limit(input, (uoff_t)-1);
+ }
+ i_stream_unref(&input);
+
+ input_list[0] = input2; input_list[1] = NULL;
+ input = i_stream_create_seekable(input_list, MAIL_MAX_MEMORY_BUFFER,
+ seekable_fd_callback, (void*)ruser);
+ i_stream_unref(&input2);
+ return input;
+}
+
+/*
+ * Init/Deinit
+ */
+
+struct mail_user *mail_raw_user_create
+(struct master_service *service, struct mail_user *mail_user)
+{
+ void **sets = master_service_settings_get_others(service);
+
+ return raw_storage_create_from_set(mail_user->set_info, sets[0]);
+}
+
+/*
+ * Open raw mail data
+ */
+
+static struct mail_raw *mail_raw_create
+(struct mail_user *ruser, struct istream *input,
+ const char *mailfile, const char *sender, time_t mtime)
+{
+ struct mail_raw *mailr;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ const char *envelope_sender, *error;
+ int ret;
+
+ if ( mailfile != NULL && *mailfile != '/' )
+ if (t_abspath(mailfile, &mailfile, &error) < 0)
+ i_fatal("t_abspath(%s) failed: %s",
+ mailfile, error);
+
+ mailr = i_new(struct mail_raw, 1);
+
+ envelope_sender = sender != NULL ? sender : DEFAULT_ENVELOPE_SENDER;
+ if ( mailfile == NULL ) {
+ ret = raw_mailbox_alloc_stream(ruser, input, mtime,
+ envelope_sender, &mailr->box);
+ } else {
+ ret = raw_mailbox_alloc_path(ruser, mailfile, (time_t)-1,
+ envelope_sender, &mailr->box);
+ }
+
+ if ( ret < 0 ) {
+ if ( mailfile == NULL ) {
+ i_fatal("Can't open delivery mail as raw: %s",
+ mailbox_get_last_internal_error(mailr->box, NULL));
+ } else {
+ i_fatal("Can't open delivery mail as raw (file=%s): %s",
+ mailfile, mailbox_get_last_internal_error(mailr->box, NULL));
+ }
+ }
+
+ mailr->trans = mailbox_transaction_begin(mailr->box, 0, __func__);
+ headers_ctx = mailbox_header_lookup_init(mailr->box, wanted_headers);
+ mailr->mail = mail_alloc(mailr->trans, 0, headers_ctx);
+ mailbox_header_lookup_unref(&headers_ctx);
+ mail_set_seq(mailr->mail, 1);
+
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_stream
+(struct mail_user *ruser, struct istream *input)
+{
+ struct mail_raw *mailr;
+
+ i_assert(input->seekable);
+ i_stream_set_name(input, "data");
+ mailr = mail_raw_create(ruser, input, NULL, NULL, (time_t)-1);
+
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_data
+(struct mail_user *ruser, string_t *mail_data)
+{
+ struct mail_raw *mailr;
+ struct istream *input;
+
+ input = i_stream_create_from_data(str_data(mail_data), str_len(mail_data));
+
+ mailr = mail_raw_open_stream(ruser, input);
+
+ i_stream_unref(&input);
+ return mailr;
+}
+
+struct mail_raw *mail_raw_open_file
+(struct mail_user *ruser, const char *path)
+{
+ struct mail_raw *mailr;
+ struct istream *input = NULL;
+ time_t mtime = (time_t)-1;
+ const char *sender = NULL;
+
+ if ( path == NULL || strcmp(path, "-") == 0 ) {
+ path = NULL;
+ input = mail_raw_create_stream(ruser, 0, &mtime, &sender);
+ }
+
+ mailr = mail_raw_create(ruser, input, path, sender, mtime);
+ i_stream_unref(&input);
+
+ return mailr;
+}
+
+void mail_raw_close(struct mail_raw **mailr)
+{
+ mail_free(&(*mailr)->mail);
+ mailbox_transaction_rollback(&(*mailr)->trans);
+ mailbox_free(&(*mailr)->box);
+
+ i_free(*mailr);
+ *mailr = NULL;
+}
+
diff --git a/pigeonhole/src/lib-sieve/util/mail-raw.h b/pigeonhole/src/lib-sieve/util/mail-raw.h
new file mode 100644
index 0000000..a942d06
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/mail-raw.h
@@ -0,0 +1,27 @@
+#ifndef MAIL_RAW_H
+#define MAIL_RAW_H
+
+#include "lib.h"
+#include "master-service.h"
+
+struct mail_raw {
+ pool_t pool;
+ struct mail *mail;
+
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+};
+
+struct mail_user *mail_raw_user_create
+ (struct master_service *service, struct mail_user *mail_user);
+
+struct mail_raw *mail_raw_open_stream
+ (struct mail_user *ruser, struct istream *input);
+struct mail_raw *mail_raw_open_file
+ (struct mail_user *ruser, const char *path);
+struct mail_raw *mail_raw_open_data
+ (struct mail_user *ruser, string_t *mail_data);
+void mail_raw_close(struct mail_raw **mailr);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/rfc2822.c b/pigeonhole/src/lib-sieve/util/rfc2822.c
new file mode 100644
index 0000000..ff3a9ad
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/rfc2822.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* NOTE: much of the functionality implemented here should eventually appear
+ * somewhere in Dovecot itself.
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "unichar.h"
+
+#include "rfc2822.h"
+
+#include "message-header-encode.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+bool rfc2822_header_field_name_verify
+(const char *field_name, unsigned int len)
+{
+ const char *p = field_name;
+ const char *pend = p + len;
+
+ /* field-name = 1*ftext
+ * ftext = %d33-57 / ; Any character except
+ * %d59-126 ; controls, SP, and
+ * ; ":".
+ */
+
+ while ( p < pend ) {
+ if ( *p < 33 || *p == ':' )
+ return FALSE;
+
+ p++;
+ }
+
+ return TRUE;
+}
+
+bool rfc2822_header_field_body_verify
+(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8)
+{
+ const unsigned char *p = (const unsigned char *)field_body;
+ const unsigned char *pend = p + len;
+ bool is8bit = FALSE;
+
+ /* RFC5322:
+ *
+ * unstructured = (*([FWS] VCHAR) *WSP)
+ * VCHAR = %x21-7E
+ * FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space
+ * WSP = SP / HTAB ; White space
+ */
+
+ while ( p < pend ) {
+ if ( *p < 0x20 ) {
+ if ( (*p == '\r' || *p == '\n') ) {
+ if ( !allow_crlf )
+ return FALSE;
+ } else if ( *p != '\t' ) {
+ return FALSE;
+ }
+ }
+
+ if ( !is8bit && *p > 127 ) {
+ if ( !allow_utf8 )
+ return FALSE;
+
+ is8bit = TRUE;
+ }
+
+ p++;
+ }
+
+ if ( is8bit && !uni_utf8_str_is_valid(field_body) ) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name)
+{
+ char *result = t_strdup_noconst(name);
+ char *p;
+
+ /* Make the whole name lower case ... */
+ result = str_lcase(result);
+
+ /* ... except for the first letter and those that follow '-' */
+ p = result;
+ *p = i_toupper(*p);
+ while ( *p != '\0' ) {
+ if ( *p == '-' ) {
+ p++;
+
+ if ( *p != '\0' )
+ *p = i_toupper(*p);
+
+ continue;
+ }
+
+ p++;
+ }
+
+ return result;
+}
+
+/*
+ * Message construction
+ */
+
+/* FIXME: This should be collected into a Dovecot API for composing internet
+ * mail messages.
+ */
+
+unsigned int rfc2822_header_append
+(string_t *header, const char *name, const char *body, bool crlf,
+ uoff_t *body_offset_r)
+{
+ static const unsigned int max_line = 80;
+
+ const char *bp = body; /* Pointer */
+ const char *sp = body; /* Start pointer */
+ const char *wp = NULL; /* Whitespace pointer */
+ const char *nlp = NULL; /* New-line pointer */
+ unsigned int line_len = strlen(name);
+ unsigned int lines = 0;
+
+ /* Write header field name first */
+ str_append(header, name);
+ str_append(header, ": ");
+
+ if ( body_offset_r != NULL )
+ *body_offset_r = str_len(header);
+
+ line_len += 2;
+
+ /* Add field body; fold it if necessary and account for existing folding */
+ while ( *bp != '\0' ) {
+ bool ws_first = TRUE;
+
+ while ( *bp != '\0' && nlp == NULL &&
+ (wp == NULL || line_len < max_line) ) {
+ if ( *bp == ' ' || *bp == '\t' ) {
+ if (ws_first)
+ wp = bp;
+ ws_first = FALSE;
+ } else if ( *bp == '\r' || *bp == '\n' ) {
+ if (ws_first)
+ nlp = bp;
+ else
+ nlp = wp;
+ break;
+ } else {
+ ws_first = TRUE;
+ }
+
+ bp++; line_len++;
+ }
+
+ if ( *bp == '\0' ) break;
+
+ /* Existing newline ? */
+ if ( nlp != NULL ) {
+ /* Replace any consecutive newline and whitespace for
+ consistency */
+ while ( *bp == ' ' || *bp == '\t' || *bp == '\r' || *bp == '\n' )
+ bp++;
+
+ str_append_data(header, sp, nlp-sp);
+
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+
+ while ( *bp == ' ' || *bp == '\t' )
+ bp++;
+ if ( *bp != '\0' ) {
+ /* Continued line; replace leading whitespace with single TAB */
+ str_append_c(header, '\t');
+ }
+
+ sp = bp;
+ } else {
+ /* Insert newline at last whitespace within the max_line limit */
+ i_assert(wp >= sp);
+ str_append_data(header, sp, wp-sp);
+
+ /* Force continued line; drop any existing whitespace */
+ while ( *wp == ' ' || *wp == '\t' )
+ wp++;
+
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+
+ /* Insert single TAB instead of the original whitespace */
+ str_append_c(header, '\t');
+
+ sp = wp;
+ if (sp > bp)
+ bp = sp;
+ }
+
+ lines++;
+
+ line_len = bp - sp;
+ wp = NULL;
+ nlp = NULL;
+ }
+
+ if ( bp != sp || lines == 0 ) {
+ str_append_data(header, sp, bp-sp);
+ if ( crlf )
+ str_append(header, "\r\n");
+ else
+ str_append(header, "\n");
+ lines++;
+ }
+
+ return lines;
+}
+
+void rfc2822_header_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+ const char *body;
+ va_list args;
+
+ va_start(args, fmt);
+ body = t_strdup_vprintf(fmt, args);
+ va_end(args);
+
+ rfc2822_header_write(header, name, body);
+}
+
+void rfc2822_header_utf8_printf
+(string_t *header, const char *name, const char *fmt, ...)
+{
+ string_t *body = t_str_new(256);
+ va_list args;
+
+ va_start(args, fmt);
+ message_header_encode(t_strdup_vprintf(fmt, args), body);
+ va_end(args);
+
+ rfc2822_header_write(header, name, str_c(body));
+}
+
+
+void rfc2822_header_write_address(string_t *header,
+ const char *name, const char *address)
+{
+ bool has_8bit = FALSE;
+ const char *p;
+
+ for (p = address; *p != '\0'; p++) {
+ if ((*p & 0x80) != 0)
+ has_8bit = TRUE;
+ }
+
+ if (!has_8bit) {
+ rfc2822_header_write(header, name, address);
+ } else {
+ string_t *body = t_str_new(256);
+ message_header_encode(address, body);
+ rfc2822_header_write(header, name, str_c(body));
+ }
+}
diff --git a/pigeonhole/src/lib-sieve/util/rfc2822.h b/pigeonhole/src/lib-sieve/util/rfc2822.h
new file mode 100644
index 0000000..02266a9
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/rfc2822.h
@@ -0,0 +1,46 @@
+#ifndef RFC2822_H
+#define RFC2822_H
+
+#include "lib.h"
+
+#include <stdio.h>
+
+/*
+ * Verification
+ */
+
+bool rfc2822_header_field_name_verify
+ (const char *field_name, unsigned int len);
+bool rfc2822_header_field_body_verify
+ (const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8);
+
+/*
+ *
+ */
+
+const char *rfc2822_header_field_name_sanitize(const char *name);
+
+/*
+ * Message composition
+ */
+
+unsigned int rfc2822_header_append
+ (string_t *header, const char *name, const char *body, bool crlf,
+ uoff_t *body_offset_r);
+
+static inline void rfc2822_header_write
+(string_t *header, const char *name, const char *body)
+{
+ (void)rfc2822_header_append(header, name, body, TRUE, NULL);
+}
+
+void rfc2822_header_printf
+ (string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+void rfc2822_header_utf8_printf
+ (string_t *header, const char *name, const char *fmt, ...) ATTR_FORMAT(3, 4);
+
+void rfc2822_header_write_address(string_t *header,
+ const char *name, const char *address);
+
+
+#endif
diff --git a/pigeonhole/src/lib-sieve/util/test-edit-mail.c b/pigeonhole/src/lib-sieve/util/test-edit-mail.c
new file mode 100644
index 0000000..0e263a2
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/test-edit-mail.c
@@ -0,0 +1,842 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "path-util.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-crlf.h"
+#include "unlink-directory.h"
+#include "master-service.h"
+#include "istream-header-filter.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-user.h"
+
+#include "mail-raw.h"
+#include "edit-mail.h"
+
+#include <time.h>
+
+static pool_t test_pool;
+
+static struct mail_storage_service_ctx *mail_storage_service = NULL;
+static struct mail_user *test_mail_user = NULL;
+static struct mail_storage_service_user *test_service_user = NULL;
+static const char *mail_home;
+static char *test_dir;
+
+static struct mail_user *test_raw_mail_user = NULL;
+
+static void str_append_no_cr(string_t *str, const char *cstr)
+{
+ const char *p, *poff;
+
+ poff = p = cstr;
+ while (*p != '\0') {
+ if (*p == '\r') {
+ str_append_data(str, poff, (p - poff));
+ poff = p+1;
+ }
+ p++;
+ }
+ str_append_data(str, poff, (p - poff));
+}
+
+static int test_init_mail_user(void)
+{
+ const char *error;
+
+ mail_home = p_strdup_printf(test_pool, "%s/test_user.%ld.%ld",
+ test_dir, (long)time(NULL), (long)getpid());
+
+ struct mail_storage_service_input input = {
+ .userdb_fields = (const char*const[]){
+ t_strdup_printf("mail=maildir:~/"),
+ t_strdup_printf("home=%s", mail_home),
+ NULL
+ },
+ .username = "test@example.com",
+ .no_userdb_lookup = TRUE,
+ .debug = TRUE,
+ };
+
+ mail_storage_service = mail_storage_service_init(
+ master_service, NULL,
+ (MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS |
+ MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+ MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS));
+
+ if (mail_storage_service_lookup(mail_storage_service, &input,
+ &test_service_user, &error) < 0)
+ {
+ i_error("Cannot lookup test user: %s", error);
+ return -1;
+ }
+
+ if (mail_storage_service_next(mail_storage_service, test_service_user,
+ &test_mail_user, &error) < 0)
+ {
+ i_error("Cannot lookup test user: %s", error);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void test_deinit_mail_user()
+{
+ const char *error;
+ mail_user_unref(&test_mail_user);
+ mail_storage_service_user_unref(&test_service_user);
+ mail_storage_service_deinit(&mail_storage_service);
+ if (unlink_directory(mail_home, UNLINK_DIRECTORY_FLAG_RMDIR,
+ &error) < 0)
+ i_error("unlink_directory(%s) failed: %s", mail_home, error);
+}
+
+static void test_init(void)
+{
+ test_pool = pool_alloconly_create(MEMPOOL_GROWING"test pool", 128);
+
+ test_init_mail_user();
+ test_raw_mail_user =
+ mail_raw_user_create(master_service, test_mail_user);
+}
+
+static void test_deinit(void)
+{
+ mail_user_unref(&test_raw_mail_user);
+ test_deinit_mail_user();
+ pool_unref(&test_pool);
+}
+
+static void test_stream_data(struct istream *input, buffer_t *buffer)
+{
+ const unsigned char *data;
+ size_t size;
+
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ buffer_append(buffer, data, size);
+ i_stream_skip(input, size);
+ }
+
+ test_assert(!i_stream_have_bytes_left(input));
+ test_assert(input->stream_errno == 0);
+}
+
+static void test_stream_data_slow(struct istream *input, buffer_t *buffer)
+{
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ ret = i_stream_read(input);
+ while (ret > 0 || i_stream_have_bytes_left(input) || ret == -2) {
+ data = i_stream_get_data(input, &size);
+ buffer_append(buffer, data, 1);
+ i_stream_skip(input, 1);
+
+ ret = i_stream_read(input);
+ }
+
+ test_assert(!i_stream_have_bytes_left(input));
+ test_assert(input->stream_errno == 0);
+}
+
+static void test_edit_mail_concatenated(void)
+{
+ static const char *hide_headers[] =
+ { "Return-Path", "X-Sieve", "X-Sieve-Redirected-From" };
+ static const char *msg_part1 =
+ "Received: from example.com ([127.0.0.1] helo=example.com)\r\n"
+ " by example.org with LMTP (Dovecot)\r\n"
+ " (envelope-from <frop-bounces@example.com>)\r\n"
+ " id 1er3e8-0015df-QO\r\n"
+ " for timo@example.org;\r\n"
+ " Sat, 03 Mar 2018 10:40:05 +0100\r\n";
+ static const char *msg_part2 =
+ "Return-Path: <stephan@example.com>\r\n";
+ static const char *msg_part3 =
+ "Delivered-To: <timo@example.org>\r\n";
+ static const char *msg_part4 =
+ "From: <stephan@example.com>\r\n"
+ "To: <timo@example.org>\r\n"
+ "Subject: Sieve editheader breaks with LMTP\r\n"
+ "\r\n"
+ "Hi,\r\n"
+ "\r\n"
+ "Sieve editheader seems to be broken when used from LMTP\r\n"
+ "\r\n"
+ "Regards,\r\n"
+ "\r\n"
+ "Stephan.\r\n";
+ static const char *msg_added =
+ "X-Filter-Junk-Type: NONE\r\n"
+ "X-Filter-Junk-Flag: NO\r\n";
+ struct istream *inputs[5], *input_msg, *input_filt, *input_mail, *input;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ string_t *expected;
+ const char *value;
+
+ test_begin("edit-mail - concatenated");
+ test_init();
+
+ /* Compose the message */
+
+ inputs[0] = i_stream_create_from_data(msg_part1, strlen(msg_part1));
+ inputs[1] = i_stream_create_from_data(msg_part2, strlen(msg_part2));
+ inputs[2] = i_stream_create_from_data(msg_part3, strlen(msg_part3));
+ inputs[3] = i_stream_create_from_data(msg_part4, strlen(msg_part4));
+ inputs[4] = NULL;
+
+ input_msg = i_stream_create_concat(inputs);
+
+ i_stream_unref(&inputs[0]);
+ i_stream_unref(&inputs[1]);
+ i_stream_unref(&inputs[2]);
+ i_stream_unref(&inputs[3]);
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ /* Add headers */
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ edit_mail_header_add(edmail, "X-Filter-Junk-Flag", "NO", FALSE);
+ edit_mail_header_add(edmail, "X-Filter-Junk-Type", "NONE", FALSE);
+
+ mail = edit_mail_get_mail(edmail);
+
+ /* Evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+ test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+ &value) > 0);
+ test_assert(strcmp(value, "NO") == 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+ &value) > 0);
+ test_assert(strcmp(value, "NONE") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+ &value) > 0);
+
+ /* Prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+ expected = t_str_new(1024);
+
+ /* Added */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+ input = input_mail;
+
+ test_stream_data(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part3);
+ str_append(expected, msg_part4);
+
+ test_out("added", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Added, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data_slow(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part3);
+ str_append(expected, msg_part4);
+
+ test_out("added, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Added, filtered */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part3);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("added, filtered",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Added, filtered, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data_slow(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part3);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("added, filtered, slow",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Delete header */
+
+ edit_mail_header_delete(edmail, "Delivered-To", 0);
+
+ /* Evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
+ test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
+ &value) > 0);
+ test_assert(strcmp(value, "NO") == 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
+ &value) > 0);
+ test_assert(strcmp(value, "NONE") == 0);
+
+ test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
+ &value) == 0);
+
+ /* Deleted */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+ input = input_mail;
+
+ test_stream_data(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part4);
+
+ test_out("deleted", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Deleted, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data_slow(input_mail, buffer);
+
+ str_truncate(expected, 0);
+ str_append(expected, msg_added);
+ str_append(expected, msg_part1);
+ str_append(expected, msg_part2);
+ str_append(expected, msg_part4);
+
+ test_out("deleted, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ /* Deleted, filtered */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(
+ input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+ hide_headers, N_ELEMENTS(hide_headers),
+ *null_header_filter_callback, (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("deleted, filtered",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* Deleted, filtered, slow */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ input_filt = i_stream_create_header_filter(input_mail,
+ HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
+ N_ELEMENTS(hide_headers), *null_header_filter_callback,
+ (void *)NULL);
+ input = i_stream_create_lf(input_filt);
+ i_stream_unref(&input_filt);
+
+ test_stream_data_slow(input, buffer);
+ test_assert(!i_stream_have_bytes_left(input_mail));
+ test_assert(input_mail->stream_errno == 0);
+
+ str_truncate(expected, 0);
+ str_append_no_cr(expected, msg_added);
+ str_append_no_cr(expected, msg_part1);
+ str_append_no_cr(expected, msg_part4);
+
+ test_out("deleted, filtered, slow",
+ strcmp(str_c(buffer), str_c(expected)) == 0);
+
+ i_stream_unref(&input);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+static const char *big_header =
+ "X-A: AAAA\n"
+ "X-Big-One: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ " AAAAAAAAAAAAAAAAAAAAAAAAA\n"
+ "X-B: BBBB\n"
+ "\n"
+ "Frop!\n";
+
+static void test_edit_mail_big_header(void)
+{
+ struct istream *input_msg, *input_mail;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ const char *value;
+
+ test_begin("edit-mail - big header");
+ test_init();
+
+ /* compose the message */
+
+ input_msg = i_stream_create_from_data(big_header, strlen(big_header));
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ /* delete header */
+
+ edit_mail_header_delete(edmail, "X-B", 0);
+ mail = edit_mail_get_mail(edmail);
+
+ /* prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+
+ /* evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "X-B", &value) == 0);
+
+ /* deleted */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data(input_mail, buffer);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+static void test_edit_mail_small_buffer(void)
+{
+ static const char *message =
+ "X-A: AAAA\n"
+ "X-B: BBBB\n"
+ "\n"
+ "Frop!\n";
+ struct istream *input_msg, *input_mail;
+ buffer_t *buffer;
+ struct mail_raw *rawmail;
+ struct edit_mail *edmail;
+ struct mail *mail;
+ const char *value;
+ unsigned int i;
+
+ test_begin("edit-mail - small buffer");
+ test_init();
+
+ /* compose the message */
+
+ input_msg = i_stream_create_from_data(message, strlen(message));
+ i_stream_set_max_buffer_size(input_msg, 16);
+
+ rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+ edmail = edit_mail_wrap(rawmail->mail);
+
+ /* add headers */
+
+ for (i = 0; i < 16; i++) {
+ edit_mail_header_add(edmail, "X-F", "FF", FALSE);
+ edit_mail_header_add(edmail, "X-L", "LL", TRUE);
+ }
+
+ mail = edit_mail_get_mail(edmail);
+
+ /* prepare tests */
+
+ if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+ i_fatal("Failed to open mail stream: %s",
+ mailbox_get_last_internal_error(mail->box, NULL));
+ }
+
+ buffer = buffer_create_dynamic(default_pool, 1024);
+
+ /* evaluate modified header */
+
+ test_assert(mail_get_first_header_utf8(mail, "X-F", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-A", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-B", &value) > 0);
+ test_assert(mail_get_first_header_utf8(mail, "X-L", &value) > 0);
+
+ /* check stream read */
+
+ i_stream_seek(input_mail, 0);
+ buffer_set_used_size(buffer, 0);
+
+ test_stream_data(input_mail, buffer);
+
+ /* clean up */
+
+ buffer_free(&buffer);
+ edit_mail_unwrap(&edmail);
+ mail_raw_close(&rawmail);
+ i_stream_unref(&input_msg);
+ test_deinit();
+ test_end();
+}
+
+int main(int argc, char *argv[])
+{
+ static void (*test_functions[])(void) = {
+ test_edit_mail_concatenated,
+ test_edit_mail_big_header,
+ test_edit_mail_small_buffer,
+ NULL
+ };
+ const enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
+ const char *cwd, *error;
+ int ret;
+
+ master_service = master_service_init("test-edit-header", service_flags,
+ &argc, &argv, "");
+ master_service_init_finish(master_service);
+
+ if (t_get_working_dir(&cwd, &error) < 0)
+ i_fatal("getcwd() failed: %s", error);
+ test_dir = i_strdup(cwd);
+
+ ret = test_run(test_functions);
+
+ i_free(test_dir);
+ master_service_deinit(&master_service);
+
+ return ret;
+}
+
diff --git a/pigeonhole/src/lib-sieve/util/test-rfc2822.c b/pigeonhole/src/lib-sieve/util/test-rfc2822.c
new file mode 100644
index 0000000..66e8ee5
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/test-rfc2822.c
@@ -0,0 +1,197 @@
+/* Copyright (c) 2018 Pigeonhole authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "str.h"
+
+#include "rfc2822.h"
+
+struct test_header_write {
+ const char *name;
+ const char *body;
+ const char *output;
+};
+
+static const struct test_header_write header_write_tests[] = {
+ {
+ .name = "Frop",
+ .body = "Bladiebla",
+ .output = "Frop: Bladiebla\r\n"
+ },{
+ .name = "Subject",
+ .body = "This is a very long subject that well exceeds the "
+ "boundary of 80 characters. It should therefore "
+ "trigger the header folding algorithm.",
+ .output =
+ "Subject: This is a very long subject that well "
+ "exceeds the boundary of 80\r\n"
+ "\tcharacters. It should therefore trigger the header "
+ "folding algorithm.\r\n"
+ },{
+ .name = "Subject",
+ .body = "This\tis\ta\tvery\tlong\tsubject\tthat\twell\texceeds"
+ "\tthe\tboundary\tof\t80\tcharacters.\tIt\tshould\t"
+ "therefore\ttrigger\tthe\theader\tfolding\talgorithm.",
+ .output =
+ "Subject: This\tis\ta\tvery\tlong\tsubject\tthat\twell"
+ "\texceeds\tthe\tboundary\tof\t80\r\n"
+ "\tcharacters.\tIt\tshould\ttherefore\ttrigger\tthe\t"
+ "header\tfolding\talgorithm.\r\n"
+ },{
+ .name = "Comment",
+ .body = "This header already contains newlines.\n"
+ "The header folding algorithm should respect these.\n"
+ "It also should convert between CRLF and LF when "
+ "needed.",
+ .output = "Comment: This header already contains newlines.\r\n"
+ "\tThe header folding algorithm should respect "
+ "these.\r\n"
+ "\tIt also should convert between CRLF and LF when "
+ "needed.\r\n"
+ },{
+ .name = "References",
+ .body = "<messageid1@example.com> <messageid2@example.com> "
+ "<extremelylonglonglonglonglonglonglonglonglonglong"
+ "longlongmessageid3@example.com> "
+ "<messageid4@example.com>",
+ .output = "References: <messageid1@example.com> "
+ "<messageid2@example.com>\r\n"
+ "\t<extremelylonglonglonglonglonglonglonglonglonglong"
+ "longlongmessageid3@example.com>\r\n"
+ "\t<messageid4@example.com>\r\n",
+ },{
+ .name = "Cc",
+ .body = "\"Richard Edgar Cipient\" "
+ "<r.e.cipient@example.com>, \"Albert Buser\" "
+ "<a.buser@example.com>, \"Steven Pammer\" "
+ "<s.pammer@example.com>",
+ .output = "Cc: \"Richard Edgar Cipient\" "
+ "<r.e.cipient@example.com>, \"Albert Buser\"\r\n"
+ "\t<a.buser@example.com>, \"Steven Pammer\" "
+ "<s.pammer@example.com>\r\n"
+ },{
+ .name = "References",
+ .body = "<00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org"
+ "> <00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.o"
+ "rg> <015c01d32023$fe3840c0$faa8c240$@karel@aa.examp"
+ "le.org> <014601d325a4$ece1ed90$c6a5c8b0$@karel@aa."
+ "example.org> <012801d32b24$7734c380$659e4a80$@karel"
+ "@aa.example.org> <00da01d32be9$2d9944b0$88cbce10$@kar"
+ "el@aa.example.org> <006a01d336ef$6825d5b0$387181"
+ "10$@karel@aa.example.org> <018501d33880$58b654f0$0a2"
+ "2fed0$@frederik@aa.example.org> <00e601d33ba3$be50f10"
+ "0$3af2d300$@frederik@aa.example.org> <016501d341ee$e"
+ "678e1a0$b36aa4e0$@frederik@aa.example.org> <00ab01"
+ "d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org> <0086"
+ "01d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org> "
+ " <019301d357e6$a2190680$e64b1380$@frederik@aa.example"
+ ".org> <025f01d384b0$24d2c"
+ "660$6e785320$@karel@aa.example.org> <01cf01d3889e$7"
+ "280cb90$578262b0$@karel@aa.example.org> <013701d38"
+ "bc2$9164b950$b42e2bf0$@karel@aa.example.org> "
+ " <014f01d3a5b1$a51afc80$ef\n"
+ " \n"
+ "\t \t \t \t \t \t \t \t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\t"
+ "r\te\tl\t@\taa.example.org> <01cb01d3af29$dd7d"
+ "1b40$987751c0$@karel@aa.example.org> "
+ " <00b401d3f2bc$6ad8c180$408a4480"
+ "$@karel@aa.example.org> <011a01d3f6ab$0eeb0480$2cc1"
+ "0d80$@frederik@aa.example.org> <005c01d3f774$37f1b210"
+ "$a7d51630$@richard@aa.example.org> <01a801d3fc2d$59"
+ "0f7730$0b2e6590$@frederik@aa.example.org> <007501d3fc"
+ "f5$23d75ce0$6b8616a0$@frederik@aa.example.org> <015d0"
+ "1d3fdbf$136da510$3a48ef30$@frederik@aa.example.org> <"
+ "021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.o"
+ "rg> <013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.exam"
+ "ple.org> <001f01d401ab$31e7b090$95b711b0$@frederik@aa"
+ ".example.org> <017201d40273$a118d200$e34a7600$@freder"
+ "ik@aa.example.org> <017401d4033e$ca3602e0$5ea208a0$@f"
+ "rederik@aa.example.org> <02a601d40404$608b9e10$21a2da"
+ "30$@frederik@aa.example.org> <014301d404d0$b65269b0$2"
+ "2f73d10$@frederik@aa.example.org> <015901d4072b$b5a1b"
+ "950$20e52bf0$@frederik@aa.example.org> <01b401d407f3$"
+ "bef52050$3cdf\n"
+ " 60 \n"
+ "\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org> <012801d"
+ "408bd$6602fce0$3208f6a0$@frederik@aa.example.org> <01"
+ "c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org"
+ "> <00ec01d40a4d$12859190$3790b4b0$@frederik@aa.exampl"
+ "e.org> <02af01d40d74$589c9050$09d5b0f0$@frederik@aa.e"
+ "xample.org> <000d01d40ec8$d3d337b0$7b79a710$@richard@"
+ "aa.example.org>\n",
+ .output = "References: <00fd01d31b6c$33d98e30$9b8caa90$@karel@aa.example.org>\r\n"
+ "\t<00f201d31c36$afbfa320$0f3ee960$@karel@aa.example.org>\r\n"
+ "\t<015c01d32023$fe3840c0$faa8c240$@karel@aa.example.org>\r\n"
+ "\t<014601d325a4$ece1ed90$c6a5c8b0$@karel@aa.example.org>\r\n"
+ "\t<012801d32b24$7734c380$659e4a80$@karel@aa.example.org>\r\n"
+ "\t<00da01d32be9$2d9944b0$88cbce10$@karel@aa.example.org>\r\n"
+ "\t<006a01d336ef$6825d5b0$38718110$@karel@aa.example.org>\r\n"
+ "\t<018501d33880$58b654f0$0a22fed0$@frederik@aa.example.org>\r\n"
+ "\t<00e601d33ba3$be50f100$3af2d300$@frederik@aa.example.org>\r\n"
+ "\t<016501d341ee$e678e1a0$b36aa4e0$@frederik@aa.example.org>\r\n"
+ "\t<00ab01d348f9$ae2e1ab0$0a8a5010$@karel@aa.example.org>\r\n"
+ "\t<008601d349c1$98ff4ba0$cafde2e0$@frederik@aa.example.org>\r\n"
+ "\t<019301d357e6$a2190680$e64b1380$@frederik@aa.example.org>\r\n"
+ "\t<025f01d384b0$24d2c660$6e785320$@karel@aa.example.org>\r\n"
+ "\t<01cf01d3889e$7280cb90$578262b0$@karel@aa.example.org>\r\n"
+ "\t<013701d38bc2$9164b950$b42e2bf0$@karel@aa.example.org>\r\n"
+ "\t<014f01d3a5b1$a51afc80$ef\r\n"
+ "\t5\t0\tf\t5\t8\t0\t$\t@\tk\ta\tr\te\tl\t@\taa.example.org>\r\n"
+ "\t<01cb01d3af29$dd7d1b40$987751c0$@karel@aa.example.org>\r\n"
+ "\t<00b401d3f2bc$6ad8c180$408a4480$@karel@aa.example.org>\r\n"
+ "\t<011a01d3f6ab$0eeb0480$2cc10d80$@frederik@aa.example.org>\r\n"
+ "\t<005c01d3f774$37f1b210$a7d51630$@richard@aa.example.org>\r\n"
+ "\t<01a801d3fc2d$590f7730$0b2e6590$@frederik@aa.example.org>\r\n"
+ "\t<007501d3fcf5$23d75ce0$6b8616a0$@frederik@aa.example.org>\r\n"
+ "\t<015d01d3fdbf$136da510$3a48ef30$@frederik@aa.example.org>\r\n"
+ "\t<021a01d3fe87$556d68b0$00483a10$@frederik@aa.example.org>\r\n"
+ "\t<013f01d3ff4e$a2d13d30$e873b790$@frederik@aa.example.org>\r\n"
+ "\t<001f01d401ab$31e7b090$95b711b0$@frederik@aa.example.org>\r\n"
+ "\t<017201d40273$a118d200$e34a7600$@frederik@aa.example.org>\r\n"
+ "\t<017401d4033e$ca3602e0$5ea208a0$@frederik@aa.example.org>\r\n"
+ "\t<02a601d40404$608b9e10$21a2da30$@frederik@aa.example.org>\r\n"
+ "\t<014301d404d0$b65269b0$22f73d10$@frederik@aa.example.org>\r\n"
+ "\t<015901d4072b$b5a1b950$20e52bf0$@frederik@aa.example.org>\r\n"
+ "\t<01b401d407f3$bef52050$3cdf\r\n"
+ "\t60\r\n"
+ "\tf0 \t$@ \tfr \ted \teri\tk@aa.example.org>\r\n"
+ "\t<012801d408bd$6602fce0$3208f6a0$@frederik@aa.example.org>\r\n"
+ "\t<01c801d40984$ae4b23c0$0ae16b40$@frederik@aa.example.org>\r\n"
+ "\t<00ec01d40a4d$12859190$3790b4b0$@frederik@aa.example.org>\r\n"
+ "\t<02af01d40d74$589c9050$09d5b0f0$@frederik@aa.example.org>\r\n"
+ "\t<000d01d40ec8$d3d337b0$7b79a710$@richard@aa.example.org>\r\n"
+ }
+};
+
+static const unsigned int header_write_tests_count =
+ N_ELEMENTS(header_write_tests);
+
+static void test_rfc2822_header_write(void)
+{
+ string_t *header;
+ unsigned int i;
+
+ test_begin("rfc2822 - header write");
+
+ header = t_str_new(1024);
+ for (i = 0; i < header_write_tests_count; i++) {
+ const struct test_header_write *test = &header_write_tests[i];
+
+ str_truncate(header, 0);
+ rfc2822_header_write(header, test->name, test->body);
+
+ test_assert_idx(strcmp(str_c(header), test->output) == 0, i);
+ }
+
+ test_end();
+}
+
+int main(void)
+{
+ static void (*test_functions[])(void) = {
+ test_rfc2822_header_write,
+ NULL
+ };
+ return test_run(test_functions);
+}
+