Coverage Report

Created: 2026-07-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
/* Notify method mailto
4
 * --------------------
5
 *
6
 * Authors: Stephan Bosch
7
 * Specification: RFC 5436
8
 * Implementation: full
9
 * Status: testing
10
 *
11
 */
12
13
/* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
14
   draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
15
   when it matures. This requires modifications to the address parser (no
16
   whitespace allowed within the address itself) and UTF-8 support will be
17
   required in the URL.
18
 */
19
20
#include "lib.h"
21
#include "array.h"
22
#include "str.h"
23
#include "ioloop.h"
24
#include "str-sanitize.h"
25
#include "ostream.h"
26
#include "settings.h"
27
#include "message-date.h"
28
#include "mail-storage.h"
29
30
#include "sieve-common.h"
31
#include "sieve-limits.h"
32
#include "sieve-address.h"
33
#include "sieve-address-source.h"
34
#include "sieve-message.h"
35
#include "sieve-smtp.h"
36
37
#include "sieve-ext-enotify.h"
38
39
#include "rfc2822.h"
40
41
#include "uri-mailto.h"
42
#include "ntfy-mailto-settings.h"
43
44
/*
45
 * Mailto notification method
46
 */
47
48
static int
49
ntfy_mailto_load(const struct sieve_enotify_method *nmth, void **context_r);
50
static void
51
ntfy_mailto_unload(const struct sieve_enotify_method *nmth);
52
53
static bool
54
ntfy_mailto_compile_check_uri(const struct sieve_enotify_env *nenv,
55
            const char *uri, const char *uri_body);
56
static bool
57
ntfy_mailto_compile_check_from(const struct sieve_enotify_env *nenv,
58
             string_t *from);
59
60
static const char *
61
ntfy_mailto_runtime_get_notify_capability(const struct sieve_enotify_env *nenv,
62
            const char *uri, const char *uri_body,
63
            const char *capability);
64
static bool
65
ntfy_mailto_runtime_check_uri(const struct sieve_enotify_env *nenv,
66
            const char *uri, const char *uri_body);
67
static bool
68
ntfy_mailto_runtime_check_operands(const struct sieve_enotify_env *nenv,
69
           const char *uri, const char *uri_body,
70
           string_t *message, string_t *from,
71
           pool_t context_pool, void **method_context);
72
73
static int
74
ntfy_mailto_action_check_duplicates(
75
  const struct sieve_enotify_env *nenv,
76
  const struct sieve_enotify_action *nact,
77
  const struct sieve_enotify_action *nact_other);
78
79
static void
80
ntfy_mailto_action_print(const struct sieve_enotify_print_env *penv,
81
       const struct sieve_enotify_action *nact);
82
83
static int
84
ntfy_mailto_action_execute(const struct sieve_enotify_exec_env *nenv,
85
         const struct sieve_enotify_action *nact);
86
87
const struct sieve_enotify_method_def mailto_notify = {
88
  "mailto",
89
  ntfy_mailto_load,
90
  ntfy_mailto_unload,
91
  ntfy_mailto_compile_check_uri,
92
  NULL,
93
  ntfy_mailto_compile_check_from,
94
  NULL,
95
  ntfy_mailto_runtime_check_uri,
96
  ntfy_mailto_runtime_get_notify_capability,
97
  ntfy_mailto_runtime_check_operands,
98
  NULL,
99
  ntfy_mailto_action_check_duplicates,
100
  ntfy_mailto_action_print,
101
  ntfy_mailto_action_execute,
102
};
103
104
/*
105
 * Reserved and unique headers
106
 */
107
108
static const char *_reserved_headers[] = {
109
  "auto-submitted",
110
  "received",
111
  "message-id",
112
  "data",
113
  "bcc",
114
  "in-reply-to",
115
  "references",
116
  "resent-date",
117
  "resent-from",
118
  "resent-sender",
119
  "resent-to",
120
  "resent-cc",
121
  "resent-bcc",
122
  "resent-msg-id",
123
  "from",
124
  "sender",
125
  NULL
126
};
127
128
static const char *_unique_headers[] = {
129
  "reply-to",
130
  NULL
131
};
132
133
/*
134
 * Method context data
135
 */
136
137
struct ntfy_mailto_action_context {
138
  struct uri_mailto *uri;
139
  const struct smtp_address *from_address;
140
};
141
142
/*
143
 * Method registration
144
 */
145
146
struct ntfy_mailto_context {
147
  const struct ntfy_mailto_settings *set;
148
};
149
150
static inline const struct ntfy_mailto_settings *
151
ntfy_mailto_get_settings(const struct sieve_enotify_env *nenv)
152
0
{
153
0
  const struct ntfy_mailto_context *mtctx = nenv->method->context;
154
155
0
  return mtctx->set;
156
0
}
157
158
static int
159
ntfy_mailto_load(const struct sieve_enotify_method *nmth, void **context_r)
160
0
{
161
0
  struct sieve_instance *svinst = nmth->svinst;
162
0
  const struct ntfy_mailto_settings *set;
163
0
  struct ntfy_mailto_context *mtctx;
164
0
  const char *error;
165
166
0
  if (settings_get(svinst->event, &ntfy_mailto_setting_parser_info, 0,
167
0
       &set, &error) < 0) {
168
0
    e_error(svinst->event, "%s", error);
169
0
    return -1;
170
0
  }
171
172
0
  mtctx = i_new(struct ntfy_mailto_context, 1);
173
0
  mtctx->set = set;
174
175
0
  *context_r = mtctx;
176
0
  return 0;
177
0
}
178
179
static void ntfy_mailto_unload(const struct sieve_enotify_method *nmth)
180
0
{
181
0
  struct ntfy_mailto_context *mtctx = nmth->context;
182
183
0
  if (mtctx == NULL)
184
0
    return;
185
0
  settings_free(mtctx->set);
186
0
  i_free(mtctx);
187
0
}
188
189
/*
190
 * URI parsing
191
 */
192
193
struct ntfy_mailto_uri_env {
194
  const struct sieve_enotify_env *nenv;
195
196
  struct event *event;
197
198
  struct uri_mailto_log uri_log;
199
};
200
201
static void ATTR_FORMAT(5, 0)
202
ntfy_mailto_uri_logv(void *context, enum log_type log_type,
203
         const char *csrc_filename, unsigned int csrc_linenum,
204
         const char *fmt, va_list args)
205
0
{
206
0
  struct ntfy_mailto_uri_env *nmuenv = context;
207
0
  const struct sieve_enotify_env *nenv = nmuenv->nenv;
208
209
0
  sieve_event_logv(nenv->svinst, nenv->ehandler, nmuenv->event,
210
0
       log_type, csrc_filename, csrc_linenum,
211
0
       nenv->location, 0, fmt, args);
212
0
}
213
214
static void
215
ntfy_mailto_uri_env_init(struct ntfy_mailto_uri_env *nmuenv,
216
       const struct sieve_enotify_env *nenv)
217
0
{
218
0
  i_zero(nmuenv);
219
0
  nmuenv->nenv = nenv;
220
0
  nmuenv->event = event_create(nenv->event);
221
0
  event_set_append_log_prefix(nmuenv->event, "mailto URI: ");
222
223
0
  nmuenv->uri_log.context = nmuenv;
224
0
  nmuenv->uri_log.logv = ntfy_mailto_uri_logv;
225
0
}
226
227
static void ntfy_mailto_uri_env_deinit(struct ntfy_mailto_uri_env *nmuenv)
228
0
{
229
0
  event_unref(&nmuenv->event);
230
0
}
231
232
/*
233
 * Validation
234
 */
235
236
237
static bool
238
ntfy_mailto_compile_check_uri(const struct sieve_enotify_env *nenv,
239
            const char *uri ATTR_UNUSED, const char *uri_body)
240
0
{
241
0
  const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv);
242
0
  struct ntfy_mailto_uri_env nmuenv;
243
0
  bool result;
244
245
0
  ntfy_mailto_uri_env_init(&nmuenv, nenv);
246
0
  result = uri_mailto_validate(
247
0
    uri_body, _reserved_headers, _unique_headers,
248
0
    set->max_recipients, set->max_headers,
249
0
    &nmuenv.uri_log);
250
0
  ntfy_mailto_uri_env_deinit(&nmuenv);
251
252
0
  return result;
253
0
}
254
255
static bool
256
ntfy_mailto_compile_check_from(const struct sieve_enotify_env *nenv,
257
             string_t *from)
258
0
{
259
0
  const char *error;
260
0
  bool result = FALSE;
261
262
0
  T_BEGIN {
263
0
    result = sieve_address_validate_str(from, &error);
264
0
    if (!result) {
265
0
      sieve_enotify_error(
266
0
        nenv,
267
0
        "specified :from address '%s' is invalid for "
268
0
        "the mailto method: %s",
269
0
        str_sanitize(str_c(from), 128), error);
270
0
    }
271
0
  } T_END;
272
273
0
  return result;
274
0
}
275
276
/*
277
 * Runtime
278
 */
279
280
struct ntfy_mailto_runtime_env {
281
  const struct sieve_enotify_env *nenv;
282
283
  struct event *event;
284
};
285
286
static const char *
287
ntfy_mailto_runtime_get_notify_capability(
288
  const struct sieve_enotify_env *nenv,
289
  const char *uri ATTR_UNUSED, const char *uri_body,
290
  const char *capability)
291
0
{
292
0
  const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv);
293
294
0
  if (!uri_mailto_validate(uri_body, _reserved_headers, _unique_headers,
295
0
         set->max_recipients, set->max_headers, NULL))
296
0
    return NULL;
297
298
0
  if (strcasecmp(capability, "online") == 0)
299
0
    return "maybe";
300
301
0
  return NULL;
302
0
}
303
304
static bool
305
ntfy_mailto_runtime_check_uri(const struct sieve_enotify_env *nenv,
306
            const char *uri ATTR_UNUSED, const char *uri_body)
307
0
{
308
0
  const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv);
309
310
0
  return uri_mailto_validate(uri_body, _reserved_headers, _unique_headers,
311
0
           set->max_recipients, set->max_headers, NULL);
312
0
}
313
314
static bool
315
ntfy_mailto_runtime_check_operands(const struct sieve_enotify_env *nenv,
316
           const char *uri ATTR_UNUSED,
317
           const char *uri_body,
318
           string_t *message ATTR_UNUSED,
319
           string_t *from, pool_t context_pool,
320
           void **method_context)
321
0
{
322
0
  const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv);
323
0
  struct ntfy_mailto_action_context *mtactx;
324
0
  struct uri_mailto *parsed_uri;
325
0
  const struct smtp_address *address;
326
0
  struct ntfy_mailto_uri_env nmuenv;
327
0
  const char *error;
328
329
  /* Need to create context before validation to have arrays present */
330
0
  mtactx = p_new(context_pool, struct ntfy_mailto_action_context, 1);
331
332
  /* Validate :from */
333
0
  if (from != NULL) {
334
0
    T_BEGIN {
335
0
      address = sieve_address_parse_str(from, &error);
336
0
      if (address == NULL) {
337
0
        sieve_enotify_error(
338
0
          nenv,
339
0
          "specified :from address '%s' is invalid for "
340
0
          "the mailto method: %s",
341
0
          str_sanitize(str_c(from), 128), error);
342
0
      } else {
343
0
        mtactx->from_address =
344
0
          smtp_address_clone(context_pool, address);
345
0
      }
346
0
    } T_END;
347
348
0
    if (address == NULL)
349
0
      return FALSE;
350
0
  }
351
352
0
  ntfy_mailto_uri_env_init(&nmuenv, nenv);
353
0
  parsed_uri = uri_mailto_parse(uri_body, context_pool,
354
0
              _reserved_headers, _unique_headers,
355
0
              set->max_recipients, set->max_headers,
356
0
              &nmuenv.uri_log);
357
0
  ntfy_mailto_uri_env_deinit(&nmuenv);
358
359
0
  if (parsed_uri == NULL)
360
0
    return FALSE;
361
362
0
  mtactx->uri = parsed_uri;
363
0
  *method_context = mtactx;
364
0
  return TRUE;
365
0
}
366
367
/*
368
 * Action duplicates
369
 */
370
371
static int
372
ntfy_mailto_action_check_duplicates(
373
  const struct sieve_enotify_env *nenv ATTR_UNUSED,
374
  const struct sieve_enotify_action *nact,
375
  const struct sieve_enotify_action *nact_other)
376
0
{
377
0
  struct ntfy_mailto_action_context *mtactx =
378
0
    nact->method_context;
379
0
  struct ntfy_mailto_action_context *mtactx_other =
380
0
    nact_other->method_context;
381
0
  const struct uri_mailto_recipient *new_rcpts, *old_rcpts;
382
0
  unsigned int new_count, old_count, i, j;
383
0
  unsigned int del_start = 0, del_len = 0;
384
385
0
  new_rcpts = array_get(&mtactx->uri->recipients, &new_count);
386
0
  old_rcpts = array_get(&mtactx_other->uri->recipients, &old_count);
387
388
0
  for (i = 0; i < new_count; i++) {
389
0
    for (j = 0; j < old_count; j++) {
390
0
      if (smtp_address_equals(new_rcpts[i].address,
391
0
            old_rcpts[j].address))
392
0
        break;
393
0
    }
394
395
0
    if (j == old_count) {
396
      /* Not duplicate */
397
0
      if (del_len > 0) {
398
        /* Perform pending deletion */
399
0
        array_delete(&mtactx->uri->recipients,
400
0
               del_start, del_len);
401
402
        /* Make sure the loop integrity is maintained */
403
0
        i -= del_len;
404
0
        new_rcpts = array_get(&mtactx->uri->recipients,
405
0
                  &new_count);
406
0
      }
407
0
      del_len = 0;
408
0
    } else {
409
      /* Mark deletion */
410
0
      if (del_len == 0)
411
0
        del_start = i;
412
0
      del_len++;
413
0
    }
414
0
  }
415
416
  /* Perform pending deletion */
417
0
  if (del_len > 0)
418
0
    array_delete(&mtactx->uri->recipients, del_start, del_len);
419
0
  return (array_count(&mtactx->uri->recipients) > 0 ? 0 : 1);
420
0
}
421
422
/*
423
 * Action printing
424
 */
425
426
static void
427
ntfy_mailto_action_print(const struct sieve_enotify_print_env *penv,
428
       const struct sieve_enotify_action *nact)
429
0
{
430
0
  unsigned int count, i;
431
0
  const struct uri_mailto_recipient *recipients;
432
0
  const struct uri_mailto_header_field *headers;
433
0
  struct ntfy_mailto_action_context *mtactx = nact->method_context;
434
435
  /* Print main method parameters */
436
437
0
  sieve_enotify_method_printf(penv, "    => importance   : %llu\n",
438
0
            (unsigned long long)nact->importance);
439
440
0
  if (nact->message != NULL) {
441
0
    sieve_enotify_method_printf(
442
0
      penv, "    => subject      : %s\n",
443
0
      nact->message);
444
0
  } else if (mtactx->uri->subject != NULL) {
445
0
    sieve_enotify_method_printf(
446
0
      penv, "    => subject      : %s\n",
447
0
      mtactx->uri->subject);
448
0
  }
449
450
0
  if (nact->from != NULL) {
451
0
    sieve_enotify_method_printf(
452
0
      penv, "    => from         : %s\n", nact->from);
453
0
  }
454
455
  /* Print mailto: recipients */
456
457
0
  sieve_enotify_method_printf(penv,   "    => recipients   :\n");
458
459
0
  recipients = array_get(&mtactx->uri->recipients, &count);
460
0
  if (count == 0) {
461
0
    sieve_enotify_method_printf(
462
0
      penv,   "       NONE, action has no effect\n");
463
0
  } else {
464
0
    for (i = 0; i < count; i++) {
465
0
      if (recipients[i].carbon_copy) {
466
0
        sieve_enotify_method_printf(
467
0
          penv, "       + Cc: %s\n",
468
0
          recipients[i].full);
469
0
      } else {
470
0
        sieve_enotify_method_printf(
471
0
          penv,   "       + To: %s\n",
472
0
          recipients[i].full);
473
0
      }
474
0
    }
475
0
  }
476
477
  /* Print accepted headers for notification message */
478
479
0
  headers = array_get(&mtactx->uri->headers, &count);
480
0
  if (count > 0) {
481
0
    sieve_enotify_method_printf(penv,   "    => headers      :\n");
482
0
    for (i = 0; i < count; i++) {
483
0
      sieve_enotify_method_printf(
484
0
        penv, "       + %s: %s\n",
485
0
        headers[i].name, headers[i].body);
486
0
    }
487
0
  }
488
489
  /* Print body for notification message */
490
491
0
  if (mtactx->uri->body != NULL) {
492
0
    sieve_enotify_method_printf(
493
0
      penv, "    => body         : \n--\n%s\n--\n",
494
0
      mtactx->uri->body);
495
0
  }
496
497
  /* Finish output with an empty line */
498
499
0
  sieve_enotify_method_printf(penv,   "\n");
500
0
}
501
502
/*
503
 * Action execution
504
 */
505
506
static bool _contains_8bit(const char *msg)
507
0
{
508
0
  const unsigned char *s = (const unsigned char *)msg;
509
510
0
  for (; *s != '\0'; s++) {
511
0
    if ((*s & 0x80) != 0)
512
0
      return TRUE;
513
0
  }
514
0
  return FALSE;
515
0
}
516
517
static int
518
ntfy_mailto_send(const struct sieve_enotify_exec_env *nenv,
519
     const struct sieve_enotify_action *nact,
520
     const struct smtp_address *owner_email)
521
0
{
522
0
  struct sieve_instance *svinst = nenv->svinst;
523
0
  const struct sieve_message_data *msgdata = nenv->msgdata;
524
0
  const struct sieve_script_env *senv = nenv->scriptenv;
525
0
  struct ntfy_mailto_action_context *mtactx = nact->method_context;
526
0
  struct ntfy_mailto_context *mtctx = nenv->method->context;
527
0
  struct sieve_address_source env_from = mtctx->set->parsed.envelope_from;
528
0
  const char *from = NULL;
529
0
  const struct smtp_address *from_smtp = NULL;
530
0
  const char *subject = mtactx->uri->subject;
531
0
  const char *body = mtactx->uri->body;
532
0
  string_t *to, *cc, *all;
533
0
  const struct uri_mailto_recipient *recipients;
534
0
  const struct uri_mailto_header_field *headers;
535
0
  struct sieve_smtp_context *sctx;
536
0
  struct ostream *output;
537
0
  string_t *msg;
538
0
  unsigned int count, i, hcount, h;
539
0
  const char *outmsgid, *error;
540
0
  int ret;
541
542
  /* Get recipients */
543
0
  recipients = array_get(&mtactx->uri->recipients, &count);
544
0
  if (count == 0) {
545
0
    sieve_enotify_warning(
546
0
      nenv, "notify mailto uri specifies no recipients; "
547
0
      "action has no effect");
548
0
    return 0;
549
0
  }
550
551
  /* Just to be sure */
552
0
  if (!sieve_smtp_available(senv)) {
553
0
    sieve_enotify_global_warning(
554
0
      nenv,
555
0
      "notify mailto method has no means to send mail");
556
0
    return 0;
557
0
  }
558
559
  /* Determine which sender to use
560
561
     From RFC 5436, Section 2.3:
562
563
     The ":from" tag overrides the default sender of the notification
564
     message.  "Sender", here, refers to the value used in the [RFC5322]
565
     "From" header.  Implementations MAY also use this value in the
566
     [RFC5321] "MAIL FROM" command (the "envelope sender"), or they may
567
     prefer to establish a mailbox that receives bounces from notification
568
     messages.
569
   */
570
0
  if ((nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
571
0
    from_smtp = sieve_message_get_sender(nenv->msgctx);
572
0
    if (from_smtp == NULL) {
573
      /* "<>" */
574
0
      i_zero(&env_from);
575
0
      env_from.type = SIEVE_ADDRESS_SOURCE_EXPLICIT;
576
0
    }
577
0
  }
578
0
  from = nact->from;
579
0
  ret = sieve_address_source_get_address(
580
0
    &env_from, svinst, senv, nenv->msgctx, nenv->flags, &from_smtp);
581
0
  if (ret < 0) {
582
0
    from_smtp = NULL;
583
0
  } else if (ret == 0) {
584
0
    if (mtactx->from_address != NULL)
585
0
      from_smtp = mtactx->from_address;
586
0
    else if (svinst->set->parsed.user_email != NULL)
587
0
      from_smtp = svinst->set->parsed.user_email;
588
0
    else {
589
0
      from_smtp = sieve_get_postmaster_smtp(senv);
590
0
      if (from == NULL)
591
0
        from = sieve_get_postmaster_address(senv);
592
0
    }
593
0
  }
594
595
  /* Determine message from address */
596
0
  if (from == NULL) {
597
0
    if (from_smtp == NULL)
598
0
      from = sieve_get_postmaster_address(senv);
599
0
    else {
600
0
      from = t_strdup_printf("<%s>",
601
0
        smtp_address_encode(from_smtp));
602
0
    }
603
0
  }
604
605
  /* Determine subject */
606
0
  if (nact->message != NULL) {
607
0
    subject = str_sanitize_utf8(
608
0
      nact->message, SIEVE_MAX_SUBJECT_HEADER_CODEPOINTS);
609
0
  } else if (subject == NULL) {
610
0
    const char *const *hsubject;
611
612
    /* Fetch subject from original message */
613
0
    if (mail_get_headers_utf8(msgdata->mail, "subject",
614
0
            &hsubject) > 0) {
615
0
      subject = str_sanitize_utf8(
616
0
        t_strdup_printf("Notification: %s", hsubject[0]),
617
0
        SIEVE_MAX_SUBJECT_HEADER_CODEPOINTS);
618
0
    } else {
619
0
      subject = "Notification: (no subject)";
620
0
    }
621
0
  }
622
623
  /* Compose To and Cc headers */
624
0
  to = NULL;
625
0
  cc = NULL;
626
0
  all = t_str_new(256);
627
0
  for (i = 0; i < count; i++) {
628
0
    if (recipients[i].carbon_copy) {
629
0
      if (cc == NULL) {
630
0
        cc = t_str_new(256);
631
0
        str_append(cc, recipients[i].full);
632
0
      } else {
633
0
        str_append(cc, ", ");
634
0
        str_append(cc, recipients[i].full);
635
0
      }
636
0
    } else {
637
0
      if (to == NULL) {
638
0
        to = t_str_new(256);
639
0
        str_append(to, recipients[i].full);
640
0
      } else {
641
0
        str_append(to, ", ");
642
0
        str_append(to, recipients[i].full);
643
0
      }
644
0
    }
645
0
    if (i < 3) {
646
0
      if (i > 0)
647
0
        str_append(all, ", ");
648
0
      str_append(
649
0
        all, smtp_address_encode_path(
650
0
          recipients[i].address));
651
0
    } else if (i == 3) {
652
0
      str_printfa(all, ", ... (%u total)", count);
653
0
    }
654
0
  }
655
656
0
  msg = t_str_new(512);
657
0
  outmsgid = sieve_message_get_new_id(svinst);
658
659
0
  rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
660
0
  rfc2822_header_write(msg, "Message-ID", outmsgid);
661
0
  rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
662
0
  rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
663
664
0
  rfc2822_header_write_address(msg, "From", from);
665
666
0
  if (to != NULL)
667
0
    rfc2822_header_write_address(msg, "To", str_c(to));
668
0
  if (cc != NULL)
669
0
    rfc2822_header_write_address(msg, "Cc", str_c(cc));
670
671
0
  rfc2822_header_printf(msg, "Auto-Submitted",
672
0
            "auto-notified; owner-email=\"%s\"",
673
0
            smtp_address_encode(owner_email));
674
0
  rfc2822_header_write(msg, "Precedence", "bulk");
675
676
  /* Set importance */
677
0
  switch (nact->importance) {
678
0
  case 1:
679
0
    rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
680
0
    rfc2822_header_write(msg, "Importance", "High");
681
0
    break;
682
0
  case 3:
683
0
    rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
684
0
    rfc2822_header_write(msg, "Importance", "Low");
685
0
    break;
686
0
  case 2:
687
0
  default:
688
0
    rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
689
0
    rfc2822_header_write(msg, "Importance", "Normal");
690
0
    break;
691
0
  }
692
693
  /* Add custom headers */
694
695
0
  headers = array_get(&mtactx->uri->headers, &hcount);
696
0
  for (h = 0; h < hcount; h++) {
697
0
    const char *name =
698
0
      rfc2822_header_field_name_sanitize(headers[h].name);
699
700
0
    rfc2822_header_write(msg, name, headers[h].body);
701
0
  }
702
703
  /* Generate message body */
704
705
0
  rfc2822_header_write(msg, "MIME-Version", "1.0");
706
0
  if (body != NULL) {
707
0
    if (_contains_8bit(body)) {
708
0
      rfc2822_header_write(msg, "Content-Type",
709
0
               "text/plain; charset=utf-8");
710
0
      rfc2822_header_write(msg, "Content-Transfer-Encoding",
711
0
               "8bit");
712
0
    } else {
713
0
      rfc2822_header_write(msg, "Content-Type",
714
0
               "text/plain; charset=us-ascii");
715
0
      rfc2822_header_write(msg, "Content-Transfer-Encoding",
716
0
               "7bit");
717
0
    }
718
0
    str_printfa(msg, "\r\n%s\r\n", body);
719
720
0
  } else {
721
0
    rfc2822_header_write(msg, "Content-Type",
722
0
             "text/plain; charset=US-ASCII");
723
0
    rfc2822_header_write(msg, "Content-Transfer-Encoding",
724
0
             "7bit");
725
726
0
    str_append(msg, "\r\nNotification of new message.\r\n");
727
0
  }
728
729
0
  sctx = sieve_smtp_start(senv, from_smtp);
730
731
  /* Send message to all recipients */
732
0
  for (i = 0; i < count; i++)
733
0
    sieve_smtp_add_rcpt(sctx, recipients[i].address);
734
735
0
  output = sieve_smtp_send(sctx);
736
0
  o_stream_nsend(output, str_data(msg), str_len(msg));
737
738
0
  if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
739
0
    if (ret < 0) {
740
0
      sieve_enotify_global_error(
741
0
        nenv, "failed to send mail notification to %s: "
742
0
        "%s (temporary failure)",
743
0
        str_c(all), str_sanitize(error, 512));
744
0
    } else {
745
0
      sieve_enotify_global_log_error(
746
0
        nenv, "failed to send mail notification to %s: "
747
0
        "%s (permanent failure)",
748
0
        str_c(all), str_sanitize(error, 512));
749
0
    }
750
0
  } else {
751
0
    struct event_passthrough *e =
752
0
      sieve_enotify_create_finish_event(nenv)->
753
0
      add_str("notify_target", str_c(all));
754
755
0
    sieve_enotify_event_log(nenv, e->event(),
756
0
          "sent mail notification to %s",
757
0
          str_c(all));
758
0
  }
759
760
0
  return 0;
761
0
}
762
763
static int
764
ntfy_mailto_action_execute(const struct sieve_enotify_exec_env *nenv,
765
         const struct sieve_enotify_action *nact)
766
0
{
767
0
  struct sieve_instance *svinst = nenv->svinst;
768
0
  const struct sieve_script_env *senv = nenv->scriptenv;
769
0
  struct mail *mail = nenv->msgdata->mail;
770
0
  const struct smtp_address *owner_email;
771
0
  const char *const *hdsp;
772
0
  int ret;
773
774
0
  owner_email = svinst->set->parsed.user_email;
775
0
  if (owner_email == NULL &&
776
0
      (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
777
0
    owner_email = sieve_message_get_final_recipient(nenv->msgctx);
778
0
  if (owner_email == NULL)
779
0
    owner_email = sieve_get_postmaster_smtp(senv);
780
0
  i_assert(owner_email != NULL);
781
782
  /* Is the message an automatic reply ? */
783
0
  ret = mail_get_headers(mail, "auto-submitted", &hdsp);
784
0
  if (ret < 0) {
785
0
    sieve_enotify_critical(
786
0
      nenv, "mailto notification: "
787
0
      "failed to read 'auto-submitted' header field",
788
0
      "mailto notification: "
789
0
      "failed to read 'auto-submitted' header field: %s",
790
0
      mailbox_get_last_internal_error(mail->box, NULL));
791
0
    return -1;
792
0
  }
793
794
  /* Theoretically multiple headers could exist, so lets make sure */
795
0
  if (ret > 0) {
796
0
    while (*hdsp != NULL) {
797
0
      if (strcasecmp(*hdsp, "no") != 0) {
798
0
        const struct smtp_address *sender = NULL;
799
0
        const char *from;
800
801
0
        if ((nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
802
0
          sender = sieve_message_get_sender(nenv->msgctx);
803
0
        from = (sender == NULL ? "" :
804
0
          t_strdup_printf(" from <%s>",
805
0
              smtp_address_encode(sender)));
806
807
0
        sieve_enotify_global_info(
808
0
          nenv, "not sending notification "
809
0
          "for auto-submitted message%s", from);
810
0
        return 0;
811
0
      }
812
0
      hdsp++;
813
0
    }
814
0
  }
815
816
0
  T_BEGIN {
817
0
    ret = ntfy_mailto_send(nenv, nact, owner_email);
818
0
  } T_END;
819
820
0
  return ret;
821
0
}