1
/* Copyright (c) 2002-2009 Dovecot Sieve authors, see the included COPYING file
4
/* Notify method mailto
7
* Authors: Stephan Bosch
8
* Specification: RFC 5436
14
/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
15
* draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
16
* when it matures. This requires modifications to the address parser (no
17
* whitespace allowed within the address itself) and UTF-8 support will be
18
* required in the URL.
25
#include "str-sanitize.h"
26
#include "message-date.h"
27
#include "mail-storage.h"
31
#include "sieve-ext-enotify.h"
32
#include "sieve-address.h"
33
#include "sieve-message.h"
39
#define NTFY_MAILTO_MAX_RECIPIENTS 8
40
#define NTFY_MAILTO_MAX_HEADERS 16
41
#define NTFY_MAILTO_MAX_SUBJECT 256
47
struct ntfy_mailto_header_field {
52
struct ntfy_mailto_recipient {
54
const char *normalized;
58
ARRAY_DEFINE_TYPE(recipients, struct ntfy_mailto_recipient);
59
ARRAY_DEFINE_TYPE(headers, struct ntfy_mailto_header_field);
62
* Mailto notification method
65
static bool ntfy_mailto_compile_check_uri
66
(const struct sieve_enotify_log *nlog, const char *uri, const char *uri_body);
67
static bool ntfy_mailto_compile_check_from
68
(const struct sieve_enotify_log *nlog, string_t *from);
70
static const char *ntfy_mailto_runtime_get_notify_capability
71
(const struct sieve_enotify_log *nlog, const char *uri, const char *uri_body,
72
const char *capability);
73
static bool ntfy_mailto_runtime_check_uri
74
(const struct sieve_enotify_log *nlog, const char *uri, const char *uri_body);
75
static bool ntfy_mailto_runtime_check_operands
76
(const struct sieve_enotify_log *nlog, const char *uri,const char *uri_body,
77
string_t *message, string_t *from, pool_t context_pool,
78
void **method_context);
80
static int ntfy_mailto_action_check_duplicates
81
(const struct sieve_enotify_log *nlog, void *method_ctx1, void *method_ctx2,
82
const char *dupl_location);
84
static void ntfy_mailto_action_print
85
(const struct sieve_enotify_print_env *penv,
86
const struct sieve_enotify_action *act);
88
static bool ntfy_mailto_action_execute
89
(const struct sieve_enotify_exec_env *nenv,
90
const struct sieve_enotify_action *act);
92
const struct sieve_enotify_method mailto_notify = {
94
ntfy_mailto_compile_check_uri,
96
ntfy_mailto_compile_check_from,
98
ntfy_mailto_runtime_check_uri,
99
ntfy_mailto_runtime_get_notify_capability,
100
ntfy_mailto_runtime_check_operands,
102
ntfy_mailto_action_check_duplicates,
103
ntfy_mailto_action_print,
104
ntfy_mailto_action_execute
108
* Method context data
111
struct ntfy_mailto_context {
112
ARRAY_TYPE(recipients) recipients;
113
ARRAY_TYPE(headers) headers;
116
const char *from_normalized;
123
static const char *_reserved_headers[] = {
143
static const char *_unique_headers[] = {
148
static inline bool _ntfy_mailto_header_allowed(const char *field_name)
150
const char **rhdr = _reserved_headers;
152
/* Check whether it is reserved */
153
while ( *rhdr != NULL ) {
154
if ( strcasecmp(field_name, *rhdr) == 0 )
162
static inline bool _ntfy_mailto_header_unique(const char *field_name)
164
const char **rhdr = _unique_headers;
166
/* Check whether it is supposed to be unique */
167
while ( *rhdr != NULL ) {
168
if ( strcasecmp(field_name, *rhdr) == 0 )
182
#define _uri_parse_error(LOG, ...) \
183
sieve_enotify_error(LOG, "invalid mailto URI: " __VA_ARGS__ )
185
#define _uri_parse_warning(LOG, ...) \
186
sieve_enotify_warning(LOG, "mailto URI: " __VA_ARGS__ )
188
/* FIXME: much of this implementation will be common to other URI schemes. This
189
* should be merged into a common implementation.
192
static const char _qchar_lookup[256] = {
193
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00
194
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10
195
0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 20
196
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 30
197
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40
198
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50
199
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60
200
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70
202
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80
203
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90
204
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A0
205
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B0
206
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C0
207
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D0
208
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E0
209
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F0
212
static inline bool _is_qchar(unsigned char c)
214
return _qchar_lookup[c];
217
static inline int _decode_hex_digit(char digit)
220
case '0': case '1': case '2': case '3': case '4':
221
case '5': case '6': case '7': case '8': case '9':
222
return (int) digit - '0';
224
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
225
return (int) digit - 'a' + 0x0a;
227
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
228
return (int) digit - 'A' + 0x0A;
234
static bool _parse_hex_value(const char **in, char *out)
238
if ( **in == '\0' || (value=_decode_hex_digit(**in)) < 0 )
244
if ( **in == '\0' || (value=_decode_hex_digit(**in)) < 0 )
249
return (*out != '\0');
252
static bool _uri_add_valid_recipient
253
(const struct sieve_enotify_log *nlog, ARRAY_TYPE(recipients) *recipients,
254
string_t *recipient, bool cc)
257
const char *normalized;
259
/* Verify recipient */
260
if ( (normalized=sieve_address_normalize(recipient, &error)) == NULL ) {
261
_uri_parse_error(nlog, "invalid recipient '%s': %s",
262
str_sanitize(str_c(recipient), 80), error);
266
/* Add recipient to the list */
267
if ( recipients != NULL ) {
268
struct ntfy_mailto_recipient *new_recipient;
269
struct ntfy_mailto_recipient *rcpts;
270
unsigned int count, i;
273
rcpts = array_get_modifiable(recipients, &count);
276
if ( count >= NTFY_MAILTO_MAX_RECIPIENTS ) {
277
if ( count == NTFY_MAILTO_MAX_RECIPIENTS ) {
278
_uri_parse_warning(nlog,
279
"more than the maximum %u recipients specified; "
280
"rest is discarded", NTFY_MAILTO_MAX_RECIPIENTS);
285
/* Check for duplicate first */
286
for ( i = 0; i < count; i++ ) {
287
if ( sieve_address_compare(rcpts[i].normalized, normalized, TRUE) == 0 )
289
/* Upgrade existing Cc: recipient to a To: recipient if possible */
290
rcpts[i].carbon_copy = ( rcpts[i].carbon_copy && cc );
292
_uri_parse_warning(nlog, "ignored duplicate recipient '%s'",
293
str_sanitize(str_c(recipient), 80));
299
pool = array_get_pool(recipients);
300
new_recipient = array_append_space(recipients);
301
new_recipient->carbon_copy = cc;
302
new_recipient->full = p_strdup(pool, str_c(recipient));
303
new_recipient->normalized = p_strdup(pool, normalized);
309
static bool _uri_parse_recipients
310
(const struct sieve_enotify_log *nlog, const char **uri_p,
311
ARRAY_TYPE(recipients) *recipients_r)
313
string_t *to = t_str_new(128);
314
const char *p = *uri_p;
316
if ( *p == '\0' || *p == '?' )
319
while ( *p != '\0' && *p != '?' ) {
321
/* % encoded character */
326
/* Parse 2-digit hex value */
327
if ( !_parse_hex_value(&p, &ch) ) {
328
_uri_parse_error(nlog, "invalid %% encoding");
332
/* Check for delimiter */
334
/* Verify and add recipient */
335
if ( !_uri_add_valid_recipient(nlog, recipients_r, to, FALSE) )
338
/* Reset for next recipient */
341
/* Content character */
342
str_append_c(to, ch);
345
if ( *p == ':' || *p == ';' || *p == ',' || !_is_qchar(*p) ) {
347
(nlog, "invalid character '%c' in 'to' part", *p);
351
/* Content character */
352
str_append_c(to, *p);
358
if ( *p != '\0' ) p++;
360
/* Verify and add recipient */
361
if ( !_uri_add_valid_recipient(nlog, recipients_r, to, FALSE) )
368
static bool _uri_parse_header_recipients
369
(const struct sieve_enotify_log *nlog, string_t *rcpt_header,
370
ARRAY_TYPE(recipients) *recipients_r, bool cc)
372
string_t *to = t_str_new(128);
373
const char *p = (const char *) str_data(rcpt_header);
374
const char *pend = p + str_len(rcpt_header);
378
/* Verify and add recipient */
379
if ( !_uri_add_valid_recipient(nlog, recipients_r, to, cc) )
382
/* Reset for next recipient */
385
/* Content character */
386
str_append_c(to, *p);
391
/* Verify and add recipient */
392
if ( !_uri_add_valid_recipient(nlog, recipients_r, to, cc) )
398
static bool _uri_header_is_duplicate
399
(ARRAY_TYPE(headers) *headers, const char *field_name)
401
if ( _ntfy_mailto_header_unique(field_name) ) {
402
const struct ntfy_mailto_header_field *hdrs;
403
unsigned int count, i;
405
hdrs = array_get(headers, &count);
406
for ( i = 0; i < count; i++ ) {
407
if ( strcasecmp(hdrs[i].name, field_name) == 0 )
415
static bool _uri_parse_headers
416
(const struct sieve_enotify_log *nlog, const char **uri_p,
417
ARRAY_TYPE(headers) *headers_r, ARRAY_TYPE(recipients) *recipients_r,
418
const char **body, const char **subject)
420
unsigned int header_count = 0;
421
string_t *field = t_str_new(128);
422
const char *p = *uri_p;
428
if ( subject != NULL )
431
if ( headers_r != NULL )
432
pool = array_get_pool(headers_r);
434
while ( *p != '\0' ) {
442
} hname_type = _HNAME_GENERIC;
443
struct ntfy_mailto_header_field *hdrf = NULL;
444
const char *field_name;
446
/* Parse field name */
447
while ( *p != '\0' && *p != '=' ) {
452
/* Encoded, parse 2-digit hex value */
453
if ( !_parse_hex_value(&p, &ch) ) {
454
_uri_parse_error(nlog, "invalid %% encoding");
457
} else if ( ch != '=' && !_is_qchar(ch) ) {
459
(nlog, "invalid character '%c' in header field name part", ch);
463
str_append_c(field, ch);
465
if ( *p != '\0' ) p++;
467
/* Verify field name */
468
if ( !rfc2822_header_field_name_verify(str_c(field), str_len(field)) ) {
469
_uri_parse_error(nlog, "invalid header field name");
473
if ( header_count >= NTFY_MAILTO_MAX_HEADERS ) {
474
/* Refuse to accept more headers than allowed by policy */
475
if ( header_count == NTFY_MAILTO_MAX_HEADERS ) {
476
_uri_parse_warning(nlog, "more than the maximum %u headers specified; "
477
"rest is discarded", NTFY_MAILTO_MAX_HEADERS);
480
hname_type = _HNAME_IGNORED;
482
/* Add new header field to array and assign its name */
484
field_name = str_c(field);
485
if ( strcasecmp(field_name, "to") == 0 )
486
hname_type = _HNAME_TO;
487
else if ( strcasecmp(field_name, "cc") == 0 )
488
hname_type = _HNAME_CC;
489
else if ( strcasecmp(field_name, "subject") == 0 )
490
hname_type = _HNAME_SUBJECT;
491
else if ( strcasecmp(field_name, "body") == 0 )
492
hname_type = _HNAME_BODY;
493
else if ( _ntfy_mailto_header_allowed(field_name) ) {
494
if ( headers_r != NULL ) {
495
if ( !_uri_header_is_duplicate(headers_r, field_name) ) {
496
hdrf = array_append_space(headers_r);
497
hdrf->name = p_strdup(pool, field_name);
499
_uri_parse_warning(nlog,
500
"ignored duplicate for unique header field '%s'",
501
str_sanitize(field_name, 32));
502
hname_type = _HNAME_IGNORED;
505
hname_type = _HNAME_IGNORED;
508
_uri_parse_warning(nlog, "ignored reserved header field '%s'",
509
str_sanitize(field_name, 32));
510
hname_type = _HNAME_IGNORED;
516
/* Reset for field body */
517
str_truncate(field, 0);
519
/* Parse field body */
520
while ( *p != '\0' && *p != '&' ) {
525
/* Encoded, parse 2-digit hex value */
526
if ( !_parse_hex_value(&p, &ch) ) {
527
_uri_parse_error(nlog, "invalid %% encoding");
530
} else if ( ch != '=' && !_is_qchar(ch) ) {
532
(nlog, "invalid character '%c' in header field value part", ch);
535
str_append_c(field, ch);
537
if ( *p != '\0' ) p++;
539
/* Verify field body */
540
if ( hname_type == _HNAME_BODY ) {
541
// FIXME: verify body ...
543
if ( !rfc2822_header_field_body_verify(str_c(field), str_len(field)) ) {
545
(nlog, "invalid header field body");
550
/* Assign field body */
552
switch ( hname_type ) {
556
/* Gracefully allow duplicate To fields */
557
if ( !_uri_parse_header_recipients(nlog, field, recipients_r, FALSE) )
561
/* Gracefully allow duplicate Cc fields */
562
if ( !_uri_parse_header_recipients(nlog, field, recipients_r, TRUE) )
566
if ( subject != NULL ) {
567
/* Igore duplicate subject field */
568
if ( *subject == NULL )
569
*subject = p_strdup(pool, str_c(field));
571
_uri_parse_warning(nlog, "ignored duplicate subject field");
575
if ( body != NULL ) {
576
/* Igore duplicate body field */
578
*body = p_strdup(pool, str_c(field));
580
_uri_parse_warning(nlog, "ignored duplicate body field");
585
hdrf->body = p_strdup(pool, str_c(field));
589
/* Reset for next name */
590
str_truncate(field, 0);
594
if ( *p != '\0' ) p++;
600
static bool ntfy_mailto_parse_uri
601
(const struct sieve_enotify_log *nlog, const char *uri_body,
602
ARRAY_TYPE(recipients) *recipients_r, ARRAY_TYPE(headers) *headers_r,
603
const char **body, const char **subject)
605
const char *p = uri_body;
608
* mailtoURI = "mailto:" [ to ] [ hfields ]
609
* to = [ addr-spec *("%2C" addr-spec ) ]
610
* hfields = "?" hfield *( "&" hfield )
611
* hfield = hfname "=" hfvalue
614
* addr-spec = local-part "@" domain
615
* local-part = dot-atom / quoted-string
616
* qchar = unreserved / pct-encoded / some-delims
617
* some-delims = "!" / "$" / "'" / "(" / ")" / "*"
618
* / "+" / "," / ";" / ":" / "@"
621
* tqchar ~= <qchar> without ";" and ":"
623
* Scheme 'mailto:' already parsed, starting parse after colon
626
/* First extract to-part by searching for '?' and decoding % items
629
if ( !_uri_parse_recipients(nlog, &p, recipients_r) )
632
/* Extract hfield items */
634
while ( *p != '\0' ) {
635
/* Extract hfield item by searching for '&' and decoding '%' items */
636
if ( !_uri_parse_headers(nlog, &p, headers_r, recipients_r, body, subject) )
647
static bool ntfy_mailto_compile_check_uri
648
(const struct sieve_enotify_log *nlog, const char *uri ATTR_UNUSED,
649
const char *uri_body)
651
ARRAY_TYPE(recipients) recipients;
652
ARRAY_TYPE(headers) headers;
653
const char *body = NULL, *subject = NULL;
655
t_array_init(&recipients, NTFY_MAILTO_MAX_RECIPIENTS);
656
t_array_init(&headers, NTFY_MAILTO_MAX_HEADERS);
658
if ( !ntfy_mailto_parse_uri
659
(nlog, uri_body, &recipients, &headers, &body, &subject) )
662
if ( array_count(&recipients) == 0 )
663
sieve_enotify_warning(nlog, "notification URI specifies no recipients");
668
static bool ntfy_mailto_compile_check_from
669
(const struct sieve_enotify_log *nlog, string_t *from)
675
result = sieve_address_validate(from, &error);
678
sieve_enotify_error(nlog,
679
"specified :from address '%s' is invalid for "
680
"the mailto method: %s",
681
str_sanitize(str_c(from), 128), error);
692
static const char *ntfy_mailto_runtime_get_notify_capability
693
(const struct sieve_enotify_log *nlog ATTR_UNUSED, const char *uri ATTR_UNUSED,
694
const char *uri_body, const char *capability)
696
if ( !ntfy_mailto_parse_uri(NULL, uri_body, NULL, NULL, NULL, NULL) ) {
700
if ( strcasecmp(capability, "online") == 0 )
706
static bool ntfy_mailto_runtime_check_uri
707
(const struct sieve_enotify_log *nlog ATTR_UNUSED, const char *uri ATTR_UNUSED,
708
const char *uri_body)
710
return ntfy_mailto_parse_uri(NULL, uri_body, NULL, NULL, NULL, NULL);
713
static bool ntfy_mailto_runtime_check_operands
714
(const struct sieve_enotify_log *nlog, const char *uri ATTR_UNUSED,
715
const char *uri_body, string_t *message ATTR_UNUSED, string_t *from,
716
pool_t context_pool, void **method_context)
718
struct ntfy_mailto_context *mtctx;
719
const char *error, *normalized;
721
/* Need to create context before validation to have arrays present */
722
mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
725
if ( from != NULL ) {
727
normalized = sieve_address_normalize(from, &error);
729
if ( normalized == NULL ) {
730
sieve_enotify_error(nlog,
731
"specified :from address '%s' is invalid for "
732
"the mailto method: %s",
733
str_sanitize(str_c(from), 128), error);
735
mtctx->from_normalized = p_strdup(context_pool, normalized);
738
if ( !normalized ) return FALSE;
741
p_array_init(&mtctx->recipients, context_pool, NTFY_MAILTO_MAX_RECIPIENTS);
742
p_array_init(&mtctx->headers, context_pool, NTFY_MAILTO_MAX_HEADERS);
744
if ( !ntfy_mailto_parse_uri
745
(nlog, uri_body, &mtctx->recipients, &mtctx->headers, &mtctx->body,
750
*method_context = (void *) mtctx;
758
static int ntfy_mailto_action_check_duplicates
759
(const struct sieve_enotify_log *nlog ATTR_UNUSED,
760
void *method_ctx1, void *method_ctx2,
761
const char *dupl_location ATTR_UNUSED)
763
struct ntfy_mailto_context *mt_new =
764
(struct ntfy_mailto_context *) method_ctx1;
765
struct ntfy_mailto_context *mt_old =
766
(struct ntfy_mailto_context *) method_ctx2;
767
const struct ntfy_mailto_recipient *new_rcpts, *old_rcpts;
768
unsigned int new_count, old_count, i, j;
769
unsigned int del_start = 0, del_len = 0;
771
new_rcpts = array_get(&mt_new->recipients, &new_count);
772
old_rcpts = array_get(&mt_old->recipients, &old_count);
774
for ( i = 0; i < new_count; i++ ) {
775
for ( j = 0; j < old_count; j++ ) {
776
if ( sieve_address_compare
777
(new_rcpts[i].normalized, old_rcpts[j].normalized, TRUE) == 0 )
781
if ( j == old_count ) {
784
/* Perform pending deletion */
785
array_delete(&mt_new->recipients, del_start, del_len);
787
/* Make sure the loop integrity is maintained */
789
new_rcpts = array_get(&mt_new->recipients, &new_count);
800
/* Perform pending deletion */
802
array_delete(&mt_new->recipients, del_start, del_len);
805
return ( array_count(&mt_new->recipients) > 0 ? 0 : 1 );
812
static void ntfy_mailto_action_print
813
(const struct sieve_enotify_print_env *penv,
814
const struct sieve_enotify_action *act)
816
unsigned int count, i;
817
const struct ntfy_mailto_recipient *recipients;
818
const struct ntfy_mailto_header_field *headers;
819
struct ntfy_mailto_context *mtctx =
820
(struct ntfy_mailto_context *) act->method_context;
822
/* Print main method parameters */
824
sieve_enotify_method_printf
825
(penv, " => importance : %d\n", act->importance);
827
if ( act->message != NULL )
828
sieve_enotify_method_printf
829
(penv, " => subject : %s\n", act->message);
830
else if ( mtctx->subject != NULL )
831
sieve_enotify_method_printf
832
(penv, " => subject : %s\n", mtctx->subject);
834
if ( act->from != NULL )
835
sieve_enotify_method_printf
836
(penv, " => from : %s\n", act->from);
838
/* Print mailto: recipients */
840
sieve_enotify_method_printf(penv, " => recipients :\n" );
842
recipients = array_get(&mtctx->recipients, &count);
844
sieve_enotify_method_printf(penv, " NONE, action has no effect\n");
846
for ( i = 0; i < count; i++ ) {
847
if ( recipients[i].carbon_copy )
848
sieve_enotify_method_printf
849
(penv, " + Cc: %s\n", recipients[i].full);
851
sieve_enotify_method_printf
852
(penv, " + To: %s\n", recipients[i].full);
856
/* Print accepted headers for notification message */
858
headers = array_get(&mtctx->headers, &count);
860
sieve_enotify_method_printf(penv, " => headers :\n" );
861
for ( i = 0; i < count; i++ ) {
862
sieve_enotify_method_printf(penv, " + %s: %s\n",
863
headers[i].name, headers[i].body);
867
/* Print body for notification message */
869
if ( mtctx->body != NULL )
870
sieve_enotify_method_printf
871
(penv, " => body : \n--\n%s\n--\n", mtctx->body);
873
/* Finish output with an empty line */
875
sieve_enotify_method_printf(penv, "\n");
882
static bool _contains_8bit(const char *msg)
884
const unsigned char *s = (const unsigned char *)msg;
886
for (; *s != '\0'; s++) {
887
if ((*s & 0x80) != 0)
894
static bool ntfy_mailto_send
895
(const struct sieve_enotify_exec_env *nenv,
896
const struct sieve_enotify_action *act, const char *recipient)
898
const struct sieve_enotify_log *nlog = nenv->notify_log;
899
const struct sieve_message_data *msgdata = nenv->msgdata;
900
const struct sieve_script_env *senv = nenv->scriptenv;
901
struct ntfy_mailto_context *mtctx =
902
(struct ntfy_mailto_context *) act->method_context;
903
const char *from = NULL, *from_smtp = NULL;
904
const char *subject = mtctx->subject;
905
const char *body = mtctx->body;
907
const struct ntfy_mailto_recipient *recipients;
909
unsigned int count, i;
911
const char *outmsgid;
914
recipients = array_get(&mtctx->recipients, &count);
916
sieve_enotify_warning(nlog,
917
"notify mailto uri specifies no recipients; action has no effect");
921
/* Just to be sure */
922
if ( senv->smtp_open == NULL || senv->smtp_close == NULL ) {
923
sieve_enotify_warning(nlog,
924
"notify mailto method has no means to send mail");
928
/* Determine message from address */
929
if ( act->from == NULL ) {
930
from = t_strdup_printf("Postmaster <%s>", senv->postmaster_address);
935
/* Determine SMTP from address */
936
if ( sieve_message_get_sender(nenv->msgctx) != NULL ) {
937
if ( mtctx->from_normalized == NULL ) {
938
from_smtp = senv->postmaster_address;
940
from_smtp = mtctx->from_normalized;
944
/* Determine subject */
945
if ( act->message != NULL ) {
946
/* FIXME: handle UTF-8 */
947
subject = str_sanitize(act->message, NTFY_MAILTO_MAX_SUBJECT);
948
} else if ( subject == NULL ) {
949
const char *const *hsubject;
951
/* Fetch subject from original message */
952
if ( mail_get_headers_utf8
953
(msgdata->mail, "subject", &hsubject) >= 0 )
954
subject = str_sanitize(t_strdup_printf("Notification: %s", hsubject[0]),
955
NTFY_MAILTO_MAX_SUBJECT);
957
subject = "Notification: (no subject)";
960
/* Compose To and Cc headers */
963
for ( i = 0; i < count; i++ ) {
964
if ( recipients[i].carbon_copy ) {
967
str_append(cc, recipients[i].full);
969
str_append(cc, ", ");
970
str_append(cc, recipients[i].full);
975
str_append(to, recipients[i].full);
977
str_append(to, ", ");
978
str_append(to, recipients[i].full);
983
/* Send message to all recipients */
984
for ( i = 0; i < count; i++ ) {
985
const struct ntfy_mailto_header_field *headers;
986
unsigned int h, hcount;
988
smtp_handle = senv->smtp_open(recipients[i].normalized, from_smtp, &f);
989
outmsgid = sieve_message_get_new_id(senv);
991
rfc2822_header_field_write(f, "X-Sieve", SIEVE_IMPLEMENTATION);
992
rfc2822_header_field_write(f, "Message-ID", outmsgid);
993
rfc2822_header_field_write(f, "Date", message_date_create(ioloop_time));
994
rfc2822_header_field_write(f, "Subject", subject);
996
rfc2822_header_field_printf(f, "From", "%s", from);
999
rfc2822_header_field_printf(f, "To", "%s", str_c(to));
1002
rfc2822_header_field_printf(f, "Cc", "%s", str_c(cc));
1004
rfc2822_header_field_printf(f, "Auto-Submitted",
1005
"auto-notified; owner-email=\"%s\"", recipient);
1006
rfc2822_header_field_write(f, "Precedence", "bulk");
1008
/* Set importance */
1009
switch ( act->importance ) {
1011
rfc2822_header_field_write(f, "X-Priority", "1 (Highest)");
1012
rfc2822_header_field_write(f, "Importance", "High");
1015
rfc2822_header_field_write(f, "X-Priority", "5 (Lowest)");
1016
rfc2822_header_field_write(f, "Importance", "Low");
1020
rfc2822_header_field_write(f, "X-Priority", "3 (Normal)");
1021
rfc2822_header_field_write(f, "Importance", "Normal");
1025
/* Add custom headers */
1027
headers = array_get(&mtctx->headers, &hcount);
1028
for ( h = 0; h < hcount; h++ ) {
1029
const char *name = rfc2822_header_field_name_sanitize(headers[h].name);
1031
rfc2822_header_field_write(f, name, headers[h].body);
1034
/* Generate message body */
1035
if ( body != NULL ) {
1036
if (_contains_8bit(body)) {
1037
rfc2822_header_field_write(f, "MIME-Version", "1.0");
1038
rfc2822_header_field_write
1039
(f, "Content-Type", "text/plain; charset=UTF-8");
1040
rfc2822_header_field_write(f, "Content-Transfer-Encoding", "8bit");
1044
fprintf(f, "%s\r\n", body);
1048
fprintf(f, "Notification of new message.\r\n");
1051
if ( senv->smtp_close(smtp_handle) ) {
1052
sieve_enotify_log(nlog,
1053
"sent mail notification to <%s>",
1054
str_sanitize(recipients[i].normalized, 80));
1056
sieve_enotify_error(nlog,
1057
"failed to send mail notification to <%s> "
1058
"(refer to system log for more information)",
1059
str_sanitize(recipients[i].normalized, 80));
1066
static bool ntfy_mailto_action_execute
1067
(const struct sieve_enotify_exec_env *nenv,
1068
const struct sieve_enotify_action *act)
1070
const char *const *headers;
1071
const char *sender = sieve_message_get_sender(nenv->msgctx);
1072
const char *recipient = sieve_message_get_recipient(nenv->msgctx);
1074
/* Is the recipient unset?
1076
if ( recipient == NULL ) {
1077
sieve_enotify_warning(nenv->notify_log,
1078
"notify mailto action aborted: envelope recipient is <>");
1082
/* Is the message an automatic reply ? */
1083
if ( mail_get_headers
1084
(nenv->msgdata->mail, "auto-submitted", &headers) >= 0 ) {
1085
const char *const *hdsp = headers;
1087
/* Theoretically multiple headers could exist, so lets make sure */
1088
while ( *hdsp != NULL ) {
1089
if ( strcasecmp(*hdsp, "no") != 0 ) {
1090
sieve_enotify_log(nenv->notify_log,
1091
"not sending notification for auto-submitted message from <%s>",
1092
str_sanitize(sender, 128));
1099
return ntfy_mailto_send(nenv, act, recipient);