Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2
 */
3
4
#include "lib.h"
5
#include "str.h"
6
#include "strfuncs.h"
7
#include "md5.h"
8
#include "hostpid.h"
9
#include "str-sanitize.h"
10
#include "ostream.h"
11
#include "message-address.h"
12
#include "message-date.h"
13
#include "var-expand.h"
14
#include "ioloop.h"
15
#include "mail-storage.h"
16
17
#include "rfc2822.h"
18
19
#include "sieve-common.h"
20
#include "sieve-limits.h"
21
#include "sieve-stringlist.h"
22
#include "sieve-code.h"
23
#include "sieve-address.h"
24
#include "sieve-extensions.h"
25
#include "sieve-commands.h"
26
#include "sieve-actions.h"
27
#include "sieve-validator.h"
28
#include "sieve-generator.h"
29
#include "sieve-interpreter.h"
30
#include "sieve-dump.h"
31
#include "sieve-result.h"
32
#include "sieve-message.h"
33
#include "sieve-smtp.h"
34
35
#include "ext-vacation-common.h"
36
37
#include <stdio.h>
38
39
/*
40
 * Forward declarations
41
 */
42
43
static const struct sieve_argument_def vacation_days_tag;
44
static const struct sieve_argument_def vacation_subject_tag;
45
static const struct sieve_argument_def vacation_from_tag;
46
static const struct sieve_argument_def vacation_addresses_tag;
47
static const struct sieve_argument_def vacation_mime_tag;
48
static const struct sieve_argument_def vacation_handle_tag;
49
50
/*
51
 * Vacation command
52
 *
53
 * Syntax:
54
 *    vacation [":days" number] [":subject" string]
55
 *                 [":from" string] [":addresses" string-list]
56
 *                 [":mime"] [":handle" string] <reason: string>
57
 */
58
59
static bool
60
cmd_vacation_registered(struct sieve_validator *valdtr,
61
      const struct sieve_extension *ext,
62
      struct sieve_command_registration *cmd_reg);
63
static bool
64
cmd_vacation_pre_validate(struct sieve_validator *valdtr,
65
        struct sieve_command *cmd);
66
static bool
67
cmd_vacation_validate(struct sieve_validator *valdtr,
68
          struct sieve_command *cmd);
69
static bool
70
cmd_vacation_generate(const struct sieve_codegen_env *cgenv,
71
          struct sieve_command *cmd);
72
73
const struct sieve_command_def vacation_command = {
74
  .identifier = "vacation",
75
  .type = SCT_COMMAND,
76
  .positional_args = 1,
77
  .subtests = 0,
78
  .block_allowed = FALSE,
79
  .block_required = FALSE,
80
  .registered = cmd_vacation_registered,
81
  .pre_validate = cmd_vacation_pre_validate,
82
  .validate = cmd_vacation_validate,
83
  .generate = cmd_vacation_generate,
84
};
85
86
/*
87
 * Vacation command tags
88
 */
89
90
/* Forward declarations */
91
92
static bool
93
cmd_vacation_validate_number_tag(struct sieve_validator *valdtr,
94
         struct sieve_ast_argument **arg,
95
         struct sieve_command *cmd);
96
static bool
97
cmd_vacation_validate_string_tag(struct sieve_validator *valdtr,
98
         struct sieve_ast_argument **arg,
99
         struct sieve_command *cmd);
100
static bool
101
cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr,
102
             struct sieve_ast_argument **arg,
103
             struct sieve_command *cmd);
104
static bool
105
cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr,
106
             struct sieve_ast_argument **arg,
107
             struct sieve_command *cmd);
108
109
/* Argument objects */
110
111
static const struct sieve_argument_def vacation_days_tag = {
112
  .identifier = "days",
113
  .validate = cmd_vacation_validate_number_tag,
114
};
115
116
static const struct sieve_argument_def vacation_seconds_tag = {
117
  .identifier = "seconds",
118
  .validate = cmd_vacation_validate_number_tag,
119
};
120
121
static const struct sieve_argument_def vacation_subject_tag = {
122
  .identifier = "subject",
123
  .validate = cmd_vacation_validate_string_tag,
124
};
125
126
static const struct sieve_argument_def vacation_from_tag = {
127
  .identifier = "from",
128
  .validate = cmd_vacation_validate_string_tag,
129
};
130
131
static const struct sieve_argument_def vacation_addresses_tag = {
132
  .identifier = "addresses",
133
  .validate = cmd_vacation_validate_stringlist_tag,
134
};
135
136
static const struct sieve_argument_def vacation_mime_tag = {
137
  .identifier = "mime",
138
  .validate = cmd_vacation_validate_mime_tag,
139
};
140
141
static const struct sieve_argument_def vacation_handle_tag = {
142
  .identifier = "handle",
143
  .validate = cmd_vacation_validate_string_tag,
144
};
145
146
/* Codes for optional arguments */
147
148
enum cmd_vacation_optional {
149
  OPT_END,
150
  OPT_SECONDS,
151
  OPT_SUBJECT,
152
  OPT_FROM,
153
  OPT_ADDRESSES,
154
  OPT_MIME,
155
};
156
157
/*
158
 * Vacation operation
159
 */
160
161
static bool
162
ext_vacation_operation_dump(const struct sieve_dumptime_env *denv,
163
          sieve_size_t *address);
164
static int
165
ext_vacation_operation_execute(const struct sieve_runtime_env *renv,
166
             sieve_size_t *address);
167
168
const struct sieve_operation_def vacation_operation = {
169
  .mnemonic = "VACATION",
170
  .ext_def = &vacation_extension,
171
  .dump = ext_vacation_operation_dump,
172
  .execute = ext_vacation_operation_execute,
173
};
174
175
/*
176
 * Vacation action
177
 */
178
179
/* Forward declarations */
180
181
static int
182
act_vacation_check_duplicate(const struct sieve_runtime_env *renv,
183
           const struct sieve_action *act,
184
           const struct sieve_action *act_other);
185
int act_vacation_check_conflict(const struct sieve_runtime_env *renv,
186
        const struct sieve_action *act,
187
        const struct sieve_action *act_other);
188
static void
189
act_vacation_print(const struct sieve_action *action,
190
       const struct sieve_result_print_env *rpenv, bool *keep);
191
static int
192
act_vacation_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
193
194
/* Action object */
195
196
const struct sieve_action_def act_vacation = {
197
  .name = "vacation",
198
  .flags = SIEVE_ACTFLAG_SENDS_RESPONSE,
199
  .check_duplicate = act_vacation_check_duplicate,
200
  .check_conflict = act_vacation_check_conflict,
201
  .print = act_vacation_print,
202
  .commit = act_vacation_commit,
203
};
204
205
/* Action context information */
206
207
struct act_vacation_context {
208
  const char *reason;
209
210
  sieve_number_t seconds;
211
  const char *subject;
212
  const char *handle;
213
  bool mime;
214
  const char *from;
215
  const struct smtp_address *from_address;
216
  const struct smtp_address *const *addresses;
217
};
218
219
/*
220
 * Command validation context
221
 */
222
223
struct cmd_vacation_context_data {
224
  string_t *from;
225
  string_t *subject;
226
227
  bool mime;
228
229
  struct sieve_ast_argument *handle_arg;
230
};
231
232
/*
233
 * Tag validation
234
 */
235
236
static bool
237
cmd_vacation_validate_number_tag(struct sieve_validator *valdtr,
238
         struct sieve_ast_argument **arg,
239
         struct sieve_command *cmd)
240
0
{
241
0
  const struct sieve_extension *ext = sieve_argument_ext(*arg);
242
0
  const struct ext_vacation_context *extctx = ext->context;
243
0
  struct sieve_ast_argument *tag = *arg;
244
0
  sieve_number_t period, seconds;
245
246
  /* Detach the tag itself */
247
0
  *arg = sieve_ast_arguments_detach(*arg,1);
248
249
  /* Check syntax:
250
   *   :days number
251
   */
252
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
253
0
            SAAT_NUMBER, FALSE))
254
0
    return FALSE;
255
256
0
  period = sieve_ast_argument_number(*arg);
257
0
  if (sieve_argument_is(tag, vacation_days_tag))
258
0
    seconds = period * (24*60*60);
259
0
  else if (sieve_argument_is(tag, vacation_seconds_tag))
260
0
    seconds = period;
261
0
  else
262
0
    i_unreached();
263
264
0
  i_assert(extctx->set->max_period > 0);
265
266
  /* Enforce :seconds >= min_period */
267
0
  if (seconds < extctx->set->min_period) {
268
0
    seconds = extctx->set->min_period;
269
270
0
    sieve_argument_validate_warning(
271
0
      valdtr, *arg,
272
0
      "specified :%s value '%llu' is under the minimum",
273
0
      sieve_argument_identifier(tag),
274
0
      (unsigned long long)period);
275
  /* Enforce :days <= max_period */
276
0
  } else if (seconds > extctx->set->max_period) {
277
0
    seconds = extctx->set->max_period;
278
279
0
    sieve_argument_validate_warning(
280
0
      valdtr, *arg,
281
0
      "specified :%s value '%llu' is over the maximum",
282
0
      sieve_argument_identifier(tag),
283
0
      (unsigned long long)period);
284
0
  }
285
286
0
  sieve_ast_argument_number_set(*arg, seconds);
287
288
  /* Skip parameter */
289
0
  *arg = sieve_ast_argument_next(*arg);
290
291
0
  return TRUE;
292
0
}
293
294
static bool
295
cmd_vacation_validate_string_tag(struct sieve_validator *valdtr,
296
         struct sieve_ast_argument **arg,
297
         struct sieve_command *cmd)
298
0
{
299
0
  struct sieve_ast_argument *tag = *arg;
300
0
  struct cmd_vacation_context_data *ctx_data =
301
0
    (struct cmd_vacation_context_data *)cmd->data;
302
303
  /* Detach the tag itself */
304
0
  *arg = sieve_ast_arguments_detach(*arg,1);
305
306
  /* Check syntax:
307
   *   :subject string
308
   *   :from string
309
   *   :handle string
310
   */
311
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
312
0
            SAAT_STRING, FALSE))
313
0
    return FALSE;
314
315
0
  if (sieve_argument_is(tag, vacation_from_tag)) {
316
0
    if (sieve_argument_is_string_literal(*arg)) {
317
0
      string_t *address = sieve_ast_argument_str(*arg);
318
0
      const char *error;
319
0
      bool result;
320
321
0
      T_BEGIN {
322
0
        result = sieve_address_validate_str(address,
323
0
                    &error);
324
325
0
        if (!result) {
326
0
          sieve_argument_validate_error(
327
0
            valdtr, *arg,
328
0
            "specified :from address '%s' is invalid for vacation action: %s",
329
0
            str_sanitize(str_c(address), 128),
330
0
            error);
331
0
        }
332
0
      } T_END;
333
334
0
      if (!result)
335
0
        return FALSE;
336
0
    }
337
338
0
    ctx_data->from = sieve_ast_argument_str(*arg);
339
340
    /* Skip parameter */
341
0
    *arg = sieve_ast_argument_next(*arg);
342
0
  } else if (sieve_argument_is(tag, vacation_subject_tag)) {
343
0
    ctx_data->subject = sieve_ast_argument_str(*arg);
344
345
    /* Skip parameter */
346
0
    *arg = sieve_ast_argument_next(*arg);
347
0
  } else if (sieve_argument_is(tag, vacation_handle_tag)) {
348
0
    ctx_data->handle_arg = *arg;
349
350
    /* Detach optional argument (emitted as mandatory) */
351
0
    *arg = sieve_ast_arguments_detach(*arg, 1);
352
0
  }
353
0
  return TRUE;
354
0
}
355
356
static bool
357
cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr,
358
             struct sieve_ast_argument **arg,
359
             struct sieve_command *cmd)
360
0
{
361
0
  struct sieve_ast_argument *tag = *arg;
362
363
  /* Detach the tag itself */
364
0
  *arg = sieve_ast_arguments_detach(*arg,1);
365
366
  /* Check syntax:
367
   *   :addresses string-list
368
   */
369
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
370
0
            SAAT_STRING_LIST, FALSE))
371
0
    return FALSE;
372
373
  /* Skip parameter */
374
0
  *arg = sieve_ast_argument_next(*arg);
375
376
0
  return TRUE;
377
0
}
378
379
static bool
380
cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr ATTR_UNUSED,
381
             struct sieve_ast_argument **arg,
382
             struct sieve_command *cmd)
383
0
{
384
0
  struct cmd_vacation_context_data *ctx_data =
385
0
    (struct cmd_vacation_context_data *)cmd->data;
386
387
0
  ctx_data->mime = TRUE;
388
389
  /* Skip tag */
390
0
  *arg = sieve_ast_argument_next(*arg);
391
392
0
  return TRUE;
393
0
}
394
395
/*
396
 * Command registration
397
 */
398
399
static bool
400
cmd_vacation_registered(struct sieve_validator *valdtr,
401
      const struct sieve_extension *ext,
402
      struct sieve_command_registration *cmd_reg)
403
0
{
404
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
405
0
             &vacation_days_tag, OPT_SECONDS);
406
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
407
0
             &vacation_subject_tag, OPT_SUBJECT);
408
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
409
0
             &vacation_from_tag, OPT_FROM);
410
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
411
0
             &vacation_addresses_tag, OPT_ADDRESSES);
412
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
413
0
             &vacation_mime_tag, OPT_MIME);
414
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
415
0
             &vacation_handle_tag, 0);
416
0
  return TRUE;
417
0
}
418
419
bool ext_vacation_register_seconds_tag(
420
  struct sieve_validator *valdtr,
421
  const struct sieve_extension *vacation_ext)
422
0
{
423
0
  sieve_validator_register_external_tag(
424
0
    valdtr, vacation_command.identifier, vacation_ext,
425
0
    &vacation_seconds_tag, OPT_SECONDS);
426
427
0
  return TRUE;
428
0
}
429
430
/*
431
 * Command validation
432
 */
433
434
static bool
435
cmd_vacation_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
436
        struct sieve_command *cmd)
437
0
{
438
0
  struct cmd_vacation_context_data *ctx_data;
439
440
  /* Assign context */
441
0
  ctx_data = p_new(sieve_command_pool(cmd),
442
0
    struct cmd_vacation_context_data, 1);
443
0
  cmd->data = ctx_data;
444
445
0
  return TRUE;
446
0
}
447
448
static const char _handle_empty_subject[] = "<default-subject>";
449
static const char _handle_empty_from[] = "<default-from>";
450
static const char _handle_mime_enabled[] = "<MIME>";
451
static const char _handle_mime_disabled[] = "<NO-MIME>";
452
453
static bool
454
cmd_vacation_validate(struct sieve_validator *valdtr,
455
          struct sieve_command *cmd)
456
0
{
457
0
  struct sieve_ast_argument *arg = cmd->first_positional;
458
0
  struct cmd_vacation_context_data *ctx_data =
459
0
    (struct cmd_vacation_context_data *)cmd->data;
460
461
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason", 1,
462
0
            SAAT_STRING))
463
0
    return FALSE;
464
465
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
466
0
    return FALSE;
467
468
  /* Construct handle if not set explicitly */
469
0
  if (ctx_data->handle_arg == NULL) {
470
0
    T_BEGIN {
471
0
      string_t *handle;
472
0
      string_t *reason = sieve_ast_argument_str(arg);
473
0
      unsigned int size = str_len(reason);
474
475
      /* Precalculate the size of it all */
476
0
      size += (ctx_data->subject == NULL ?
477
0
         sizeof(_handle_empty_subject) - 1 :
478
0
         str_len(ctx_data->subject));
479
0
      size += (ctx_data->from == NULL ?
480
0
         sizeof(_handle_empty_from) - 1 :
481
0
         str_len(ctx_data->from));
482
0
      size += (ctx_data->mime ?
483
0
         sizeof(_handle_mime_enabled) - 1 :
484
0
         sizeof(_handle_mime_disabled) - 1);
485
486
      /* Construct the string */
487
0
      handle = t_str_new(size);
488
0
      str_append_str(handle, reason);
489
490
0
      if (ctx_data->subject != NULL)
491
0
        str_append_str(handle, ctx_data->subject);
492
0
      else
493
0
        str_append(handle, _handle_empty_subject);
494
495
0
      if (ctx_data->from != NULL)
496
0
        str_append_str(handle, ctx_data->from);
497
0
      else
498
0
        str_append(handle, _handle_empty_from);
499
500
0
      str_append(handle, (ctx_data->mime ?
501
0
              _handle_mime_enabled :
502
0
              _handle_mime_disabled));
503
504
      /* Create positional handle argument */
505
0
      ctx_data->handle_arg =
506
0
        sieve_ast_argument_string_create(
507
0
          cmd->ast_node, handle,
508
0
          sieve_ast_node_line(cmd->ast_node));
509
0
    } T_END;
510
511
0
    if (!sieve_validator_argument_activate(
512
0
      valdtr, cmd, ctx_data->handle_arg, TRUE))
513
0
      return FALSE;
514
0
  } else {
515
    /* Attach explicit handle argument as positional */
516
0
    (void)sieve_ast_argument_attach(cmd->ast_node,
517
0
            ctx_data->handle_arg);
518
0
  }
519
520
0
  return TRUE;
521
0
}
522
523
/*
524
 * Code generation
525
 */
526
527
static bool
528
cmd_vacation_generate(const struct sieve_codegen_env *cgenv,
529
          struct sieve_command *cmd)
530
0
{
531
0
  sieve_operation_emit(cgenv->sblock, cmd->ext, &vacation_operation);
532
533
  /* Generate arguments */
534
0
  if (!sieve_generate_arguments(cgenv, cmd, NULL))
535
0
    return FALSE;
536
0
  return TRUE;
537
0
}
538
539
/*
540
 * Code dump
541
 */
542
543
static bool
544
ext_vacation_operation_dump(const struct sieve_dumptime_env *denv,
545
          sieve_size_t *address)
546
0
{
547
0
  int opt_code = 0;
548
549
0
  sieve_code_dumpf(denv, "VACATION");
550
0
  sieve_code_descend(denv);
551
552
  /* Dump optional operands */
553
554
0
  for (;;) {
555
0
    int opt;
556
0
    bool opok = TRUE;
557
558
0
    if ((opt = sieve_opr_optional_dump(denv, address,
559
0
               &opt_code)) < 0)
560
0
      return FALSE;
561
562
0
    if (opt == 0)
563
0
      break;
564
565
0
    switch (opt_code) {
566
0
    case OPT_SECONDS:
567
0
      opok = sieve_opr_number_dump(denv, address, "seconds");
568
0
      break;
569
0
    case OPT_SUBJECT:
570
0
      opok = sieve_opr_string_dump(denv, address, "subject");
571
0
      break;
572
0
    case OPT_FROM:
573
0
      opok = sieve_opr_string_dump(denv, address, "from");
574
0
      break;
575
0
    case OPT_ADDRESSES:
576
0
      opok = sieve_opr_stringlist_dump(denv, address,
577
0
               "addresses");
578
0
      break;
579
0
    case OPT_MIME:
580
0
      sieve_code_dumpf(denv, "mime");
581
0
      break;
582
0
    default:
583
0
      return FALSE;
584
0
    }
585
586
0
    if (!opok)
587
0
      return FALSE;
588
0
  }
589
590
  /* Dump reason and handle operands */
591
0
  return (sieve_opr_string_dump(denv, address, "reason") &&
592
0
    sieve_opr_string_dump(denv, address, "handle"));
593
0
}
594
595
/*
596
 * Code execution
597
 */
598
599
static int
600
ext_vacation_operation_execute(const struct sieve_runtime_env *renv,
601
             sieve_size_t *address)
602
0
{
603
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
604
0
  const struct ext_vacation_context *extctx = this_ext->context;
605
0
  struct sieve_side_effects_list *slist = NULL;
606
0
  struct act_vacation_context *act;
607
0
  pool_t pool;
608
0
  int opt_code = 0;
609
0
  sieve_number_t seconds = extctx->set->default_period;
610
0
  bool mime = FALSE;
611
0
  struct sieve_stringlist *addresses = NULL;
612
0
  string_t *reason, *subject = NULL, *from = NULL, *handle = NULL;
613
0
  const struct smtp_address *from_address = NULL;
614
0
  int ret;
615
616
  /*
617
   * Read code
618
   */
619
620
  /* Optional operands */
621
622
0
  for (;;) {
623
0
    int opt;
624
625
0
    if ((opt = sieve_opr_optional_read(renv, address,
626
0
               &opt_code)) < 0)
627
0
      return SIEVE_EXEC_BIN_CORRUPT;
628
629
0
    if (opt == 0)
630
0
      break;
631
632
0
    switch (opt_code) {
633
0
    case OPT_SECONDS:
634
0
      ret = sieve_opr_number_read(renv, address, "seconds",
635
0
                &seconds);
636
0
      break;
637
0
    case OPT_SUBJECT:
638
0
      ret = sieve_opr_string_read(renv, address, "subject",
639
0
                &subject);
640
0
      break;
641
0
    case OPT_FROM:
642
0
      ret = sieve_opr_string_read(renv, address, "from",
643
0
                &from);
644
0
      break;
645
0
    case OPT_ADDRESSES:
646
0
      ret = sieve_opr_stringlist_read(renv, address,
647
0
              "addresses",
648
0
              &addresses);
649
0
      break;
650
0
    case OPT_MIME:
651
0
      mime = TRUE;
652
0
      ret = SIEVE_EXEC_OK;
653
0
      break;
654
0
    default:
655
0
      sieve_runtime_trace_error(
656
0
        renv, "unknown optional operand");
657
0
      ret = SIEVE_EXEC_BIN_CORRUPT;
658
0
    }
659
660
0
    if (ret <= 0)
661
0
      return ret;
662
0
  }
663
664
  /* Fixed operands */
665
666
0
  ret = sieve_opr_string_read(renv, address, "reason", &reason);
667
0
  if (ret <= 0)
668
0
    return ret;
669
0
  ret = sieve_opr_string_read(renv, address, "handle", &handle);
670
0
  if (ret <= 0)
671
0
    return ret;
672
673
  /*
674
   * Perform operation
675
   */
676
677
  /* Trace */
678
679
0
  if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
680
0
    sieve_runtime_trace(renv, 0, "vacation action");
681
0
    sieve_runtime_trace_descend(renv);
682
0
    sieve_runtime_trace(renv, 0, "auto-reply with message '%s'",
683
0
            str_sanitize(str_c(reason), 80));
684
0
  }
685
686
  /* Parse :from address */
687
0
  if (from != NULL) {
688
0
    const char *error;
689
690
0
    from_address = sieve_address_parse_str(from, &error);
691
0
    if (from_address == NULL) {
692
0
      sieve_runtime_error(
693
0
        renv, NULL,
694
0
        "specified :from address '%s' is invalid for vacation action: %s",
695
0
        str_sanitize(str_c(from), 128), error);
696
0
      }
697
0
  }
698
699
  /* Add vacation action to the result */
700
701
0
  pool = sieve_result_pool(renv->result);
702
0
  act = p_new(pool, struct act_vacation_context, 1);
703
0
  act->reason = p_strdup(pool, str_c(reason));
704
0
  act->handle = p_strdup(pool, str_c(handle));
705
0
  act->seconds = seconds;
706
0
  act->mime = mime;
707
0
  if (subject != NULL)
708
0
    act->subject = p_strdup(pool, str_c(subject));
709
0
  if (from != NULL) {
710
0
    act->from = p_strdup(pool, str_c(from));
711
0
    act->from_address = smtp_address_clone(pool, from_address);
712
0
  }
713
714
  /* Normalize all addresses */
715
0
  if (addresses != NULL) {
716
0
    ARRAY_TYPE(smtp_address_const) addrs;
717
0
    string_t *raw_address;
718
0
    int ret;
719
720
0
    sieve_stringlist_reset(addresses);
721
722
0
    p_array_init(&addrs, pool, 4);
723
724
0
    raw_address = NULL;
725
0
    while ((ret = sieve_stringlist_next_item(addresses,
726
0
               &raw_address)) > 0) {
727
0
      const struct smtp_address *addr;
728
0
      const char *error;
729
730
0
      addr = sieve_address_parse_str(raw_address, &error);
731
0
      if (addr != NULL) {
732
0
        addr = smtp_address_clone(pool, addr);
733
0
        array_append(&addrs, &addr, 1);
734
0
      } else {
735
0
        sieve_runtime_error(
736
0
          renv, NULL,
737
0
          "specified :addresses item '%s' is invalid: "
738
0
          "%s for vacation action (ignored)",
739
0
          str_sanitize(str_c(raw_address),128),
740
0
          error);
741
0
      }
742
0
    }
743
744
0
    if (ret < 0) {
745
0
      sieve_runtime_trace_error(
746
0
        renv, "invalid addresses stringlist");
747
0
      return SIEVE_EXEC_BIN_CORRUPT;
748
0
    }
749
750
0
    (void)array_append_space(&addrs);
751
0
    act->addresses = array_idx(&addrs, 0);
752
0
  }
753
754
0
  if (sieve_result_add_action(renv, this_ext, "vacation", &act_vacation,
755
0
            slist, act, 0, FALSE) < 0)
756
0
    return SIEVE_EXEC_FAILURE;
757
758
0
  return SIEVE_EXEC_OK;
759
0
}
760
761
/*
762
 * Action
763
 */
764
765
/* Runtime verification */
766
767
static int
768
act_vacation_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
769
           const struct sieve_action *act,
770
           const struct sieve_action *act_other)
771
0
{
772
0
  if (!sieve_action_is_executed(act_other, renv->result)) {
773
0
    sieve_runtime_error(
774
0
      renv, act->location,
775
0
      "duplicate vacation action not allowed "
776
0
      "(previously triggered one was here: %s)",
777
0
      act_other->location);
778
0
    return -1;
779
0
  }
780
781
  /* Not an error if executed in preceeding script */
782
0
  return 1;
783
0
}
784
785
int act_vacation_check_conflict(const struct sieve_runtime_env *renv,
786
        const struct sieve_action *act,
787
        const struct sieve_action *act_other)
788
0
{
789
0
  if ((act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0) {
790
0
    if (!sieve_action_is_executed(act_other, renv->result)) {
791
0
      sieve_runtime_error(
792
0
        renv, act->location,
793
0
        "vacation action conflicts with other action: "
794
0
        "the %s action (%s) also sends a response back to the sender",
795
0
        act_other->def->name, act_other->location);
796
0
      return -1;
797
0
    } else {
798
      /* Not an error if executed in preceeding script */
799
0
      return 1;
800
0
    }
801
0
  }
802
803
0
  return 0;
804
0
}
805
806
/* Result printing */
807
808
static void act_vacation_print(const struct sieve_action *action ATTR_UNUSED,
809
             const struct sieve_result_print_env *rpenv,
810
             bool *keep ATTR_UNUSED)
811
0
{
812
0
  struct act_vacation_context *ctx =
813
0
    (struct act_vacation_context *)action->context;
814
815
0
  sieve_result_action_printf(rpenv, "send vacation message:");
816
0
  sieve_result_printf(rpenv, "    => seconds : %llu\n",
817
0
          (unsigned long long)ctx->seconds);
818
0
  if (ctx->subject != NULL) {
819
0
    sieve_result_printf(rpenv, "    => subject : %s\n",
820
0
            ctx->subject);
821
0
  }
822
0
  if (ctx->from != NULL) {
823
0
    sieve_result_printf(rpenv, "    => from    : %s\n",
824
0
            ctx->from);
825
0
  }
826
0
  if (ctx->handle != NULL) {
827
0
    sieve_result_printf(rpenv, "    => handle  : %s\n",
828
0
            ctx->handle);
829
0
  }
830
0
  sieve_result_printf(rpenv, "\nSTART MESSAGE\n%s\nEND MESSAGE\n",
831
0
          ctx->reason);
832
0
}
833
834
/* Result execution */
835
836
/* Headers known to be associated with mailing lists
837
 */
838
static const char *const _list_headers[] = {
839
  "list-id",
840
  "list-owner",
841
  "list-subscribe",
842
  "list-post",
843
  "list-unsubscribe",
844
  "list-help",
845
  "list-archive",
846
  NULL
847
};
848
849
/* Headers that should be searched for the user's own mail address(es)
850
 */
851
852
static const char *const _my_address_headers[] = {
853
  "to",
854
  "cc",
855
  "bcc",
856
  "resent-to",
857
  "resent-cc",
858
  "resent-bcc",
859
  NULL
860
};
861
862
/* Headers that should be searched for the full sender address
863
 */
864
865
static const char *const _sender_headers[] = {
866
  "sender",
867
  "resent-from",
868
  "from",
869
  NULL
870
};
871
872
static inline bool _is_system_address(const struct smtp_address *address)
873
0
{
874
0
  if (strcasecmp(address->localpart, "MAILER-DAEMON") == 0)
875
0
    return TRUE;
876
0
  if (strcasecmp(address->localpart, "LISTSERV") == 0)
877
0
    return TRUE;
878
0
  if (strcasecmp(address->localpart, "majordomo") == 0)
879
0
    return TRUE;
880
0
  if (strstr(address->localpart, "-request") != NULL)
881
0
    return TRUE;
882
0
  if (str_begins_with(address->localpart, "owner-"))
883
0
    return TRUE;
884
0
  return FALSE;
885
0
}
886
887
static bool
888
_msg_address_equals(const struct message_address *addr1,
889
        const struct smtp_address *addr2)
890
0
{
891
0
  struct smtp_address saddr;
892
893
0
  i_assert(addr1->mailbox != NULL);
894
0
  return (smtp_address_init_from_msg(&saddr, addr1) >= 0 &&
895
0
    smtp_address_equals_icase(addr2, &saddr));
896
0
}
897
898
static inline bool
899
_header_contains_my_address(const char *header_val,
900
          const struct smtp_address *my_address)
901
0
{
902
0
  const struct message_address *msg_addr;
903
904
0
  msg_addr = message_address_parse(pool_datastack_create(),
905
0
           (const unsigned char *)header_val,
906
0
           strlen(header_val), 256, 0);
907
0
  while (msg_addr != NULL) {
908
0
    if (msg_addr->domain != NULL) {
909
0
      if (_msg_address_equals(msg_addr, my_address))
910
0
        return TRUE;
911
0
    }
912
913
0
    msg_addr = msg_addr->next;
914
0
  }
915
916
0
  return FALSE;
917
0
}
918
919
static inline bool
920
_contains_my_address(const char *const *headers,
921
         const struct smtp_address *my_address)
922
0
{
923
0
  const char *const *hdsp = headers;
924
925
0
  while (*hdsp != NULL) {
926
0
    bool result;
927
928
0
    T_BEGIN {
929
0
      result = _header_contains_my_address(*hdsp, my_address);
930
0
    } T_END;
931
932
0
    if (result)
933
0
      return TRUE;
934
935
0
    hdsp++;
936
0
  }
937
938
0
  return FALSE;
939
0
}
940
941
static bool _contains_8bit(const char *text)
942
0
{
943
0
  const unsigned char *p = (const unsigned char *)text;
944
945
0
  for (; *p != '\0'; p++) {
946
0
    if ((*p & 0x80) != 0)
947
0
      return TRUE;
948
0
  }
949
0
  return FALSE;
950
0
}
951
952
static bool
953
_header_get_full_reply_recipient(const struct ext_vacation_context *extctx,
954
         const struct smtp_address *smtp_to,
955
         const char *header,
956
         struct message_address *reply_to_r)
957
0
{
958
0
  const struct message_address *addr;
959
960
0
  addr = message_address_parse(
961
0
    pool_datastack_create(),
962
0
    (const unsigned char *)header,
963
0
    strlen(header), 256, 0);
964
965
0
  for (; addr != NULL; addr = addr->next) {
966
0
    bool matched = extctx->set->to_header_ignore_envelope;
967
968
0
    if (addr->domain == NULL || addr->invalid_syntax)
969
0
      continue;
970
971
0
    if (!matched)
972
0
      matched = _msg_address_equals(addr, smtp_to);
973
974
0
    if (matched) {
975
0
      *reply_to_r = *addr;
976
0
      return TRUE;
977
0
    }
978
0
  }
979
0
  return FALSE;
980
0
}
981
982
static int
983
_get_full_reply_recipient(const struct sieve_action_exec_env *aenv,
984
        const struct ext_vacation_context *extctx,
985
        const struct smtp_address *smtp_to,
986
        struct message_address *reply_to_r)
987
0
{
988
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
989
0
  const struct sieve_message_data *msgdata = eenv->msgdata;
990
0
  const char *const *hdsp;
991
0
  int ret;
992
993
0
  hdsp = _sender_headers;
994
0
  for (; *hdsp != NULL; hdsp++) {
995
0
    const char *header;
996
997
0
    ret = mail_get_first_header(msgdata->mail, *hdsp, &header);
998
0
    if (ret < 0) {
999
0
      return sieve_result_mail_error(
1000
0
        aenv, msgdata->mail,
1001
0
        "failed to read header field '%s'", *hdsp);
1002
0
    }
1003
0
    if (ret == 0 || header == NULL)
1004
0
      continue;
1005
1006
0
    if (_header_get_full_reply_recipient(extctx, smtp_to,
1007
0
                 header, reply_to_r))
1008
0
      return SIEVE_EXEC_OK;
1009
0
  }
1010
1011
0
  reply_to_r->mailbox = smtp_to->localpart;
1012
0
  reply_to_r->domain = smtp_to->domain;
1013
0
  return SIEVE_EXEC_OK;
1014
0
}
1015
1016
static const struct var_expand_table *
1017
_get_var_expand_table(const struct sieve_action_exec_env *aenv ATTR_UNUSED,
1018
          const char *subject)
1019
0
{
1020
0
  const struct var_expand_table stack_tab[] = {
1021
0
    { .key = "subject", .value = subject },
1022
0
    VAR_EXPAND_TABLE_END
1023
0
  };
1024
1025
0
  return p_memdup(unsafe_data_stack_pool, stack_tab, sizeof(stack_tab));
1026
0
}
1027
1028
static int
1029
act_vacation_get_default_subject(const struct sieve_action_exec_env *aenv,
1030
         const struct ext_vacation_context *extctx,
1031
         const char **subject_r)
1032
0
{
1033
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1034
0
  const struct sieve_message_data *msgdata = eenv->msgdata;
1035
0
  const char *header, *error;
1036
0
  string_t *str;
1037
0
  int ret;
1038
1039
0
  *subject_r = (*extctx->set->default_subject == '\0' ?
1040
0
          "Automated reply" : extctx->set->default_subject);
1041
0
  ret = mail_get_first_header_utf8(msgdata->mail, "subject", &header);
1042
0
  if (ret < 0) {
1043
0
    return sieve_result_mail_error(
1044
0
      aenv, msgdata->mail,
1045
0
      "failed to read header field 'subject'");
1046
0
  }
1047
0
  if (ret == 0)
1048
0
    return SIEVE_EXEC_OK;
1049
0
  if (*extctx->set->default_subject_template == '\0') {
1050
0
    *subject_r = t_strconcat("Auto: ", header, NULL);
1051
0
    return SIEVE_EXEC_OK;
1052
0
  }
1053
1054
0
  str = t_str_new(256);
1055
0
  const struct var_expand_params params = {
1056
0
    .table = _get_var_expand_table(aenv, header),
1057
0
  };
1058
0
  if (var_expand(str, extctx->set->default_subject_template, &params,
1059
0
           &error) < 0) {
1060
0
    e_error(aenv->event,
1061
0
      "Failed to expand deliver_log_format=%s: %s",
1062
0
      extctx->set->default_subject_template, error);
1063
0
    *subject_r = t_strconcat("Auto: ", header, NULL);
1064
0
    return SIEVE_EXEC_OK;
1065
0
  }
1066
1067
0
  *subject_r = str_c(str);
1068
0
  return SIEVE_EXEC_OK;
1069
0
}
1070
1071
static int
1072
act_vacation_send(const struct sieve_action_exec_env *aenv,
1073
      const struct ext_vacation_context *extctx,
1074
      struct act_vacation_context *actx,
1075
      const struct smtp_address *smtp_to,
1076
      const struct smtp_address *smtp_from,
1077
      const struct message_address *reply_from)
1078
0
{
1079
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1080
0
  const struct sieve_message_data *msgdata = eenv->msgdata;
1081
0
  const struct sieve_script_env *senv = eenv->scriptenv;
1082
0
  struct sieve_smtp_context *sctx;
1083
0
  struct ostream *output;
1084
0
  string_t *msg;
1085
0
  struct message_address reply_to;
1086
0
  const char *header, *outmsgid, *subject, *error;
1087
0
  int ret;
1088
1089
  /* Check smpt functions just to be sure */
1090
1091
0
  if (!sieve_smtp_available(senv)) {
1092
0
    sieve_result_global_warning(
1093
0
      aenv, "vacation action has no means to send mail");
1094
0
    return SIEVE_EXEC_OK;
1095
0
  }
1096
1097
  /* Make sure we have a subject for our reply */
1098
1099
0
  if (actx->subject == NULL || *(actx->subject) == '\0') {
1100
0
    ret = act_vacation_get_default_subject(aenv, extctx, &subject);
1101
0
    if (ret <= 0)
1102
0
      return ret;
1103
0
  } else {
1104
0
    subject = actx->subject;
1105
0
  }
1106
1107
0
  subject = str_sanitize_utf8(
1108
0
    subject, SIEVE_MAX_SUBJECT_HEADER_CODEPOINTS);
1109
1110
  /* Obtain full To address for reply */
1111
1112
0
  i_zero(&reply_to);
1113
0
  reply_to.mailbox = smtp_to->localpart;
1114
0
  reply_to.domain = smtp_to->domain;
1115
0
  ret = _get_full_reply_recipient(aenv, extctx, smtp_to, &reply_to);
1116
0
  if (ret <= 0)
1117
0
    return ret;
1118
1119
  /* Open smtp session */
1120
1121
0
  sctx = sieve_smtp_start_single(senv, smtp_to, smtp_from, &output);
1122
1123
0
  outmsgid = sieve_message_get_new_id(eenv->svinst);
1124
1125
  /* Produce a proper reply */
1126
1127
0
  msg = t_str_new(512);
1128
0
  rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
1129
0
  rfc2822_header_write(msg, "Message-ID", outmsgid);
1130
0
  rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
1131
1132
0
  if (actx->from != NULL && *(actx->from) != '\0') {
1133
0
    rfc2822_header_write_address(msg, "From", actx->from);
1134
0
  } else {
1135
0
    if (reply_from == NULL || reply_from->mailbox == NULL ||
1136
0
        *reply_from->mailbox == '\0')
1137
0
      reply_from = sieve_get_postmaster(senv);
1138
0
    rfc2822_header_write(
1139
0
      msg, "From",
1140
0
      message_address_first_to_string(reply_from));
1141
0
  }
1142
1143
0
  rfc2822_header_write(msg, "To",
1144
0
           message_address_first_to_string(&reply_to));
1145
1146
0
  if (_contains_8bit(subject))
1147
0
    rfc2822_header_utf8_printf(msg, "Subject", "%s", subject);
1148
0
  else
1149
0
    rfc2822_header_printf(msg, "Subject", "%s", subject);
1150
1151
  /* Compose proper in-reply-to and references headers */
1152
1153
0
  ret = mail_get_first_header(msgdata->mail, "references", &header);
1154
0
  if (ret < 0) {
1155
0
    sieve_smtp_abort(sctx);
1156
0
    return sieve_result_mail_error(
1157
0
      aenv, msgdata->mail,
1158
0
      "failed to read header field 'references'");
1159
0
  }
1160
1161
0
  if (msgdata->id != NULL) {
1162
0
    rfc2822_header_write(msg, "In-Reply-To", msgdata->id);
1163
1164
0
    if (ret > 0 && header != NULL) {
1165
0
      rfc2822_header_write(
1166
0
        msg, "References",
1167
0
        t_strconcat(header, " ", msgdata->id, NULL));
1168
0
    } else {
1169
0
      rfc2822_header_write(msg, "References", msgdata->id);
1170
0
    }
1171
0
  } else if (ret > 0 && header != NULL) {
1172
0
    rfc2822_header_write(msg, "References", header);
1173
0
  }
1174
1175
0
  rfc2822_header_write(msg, "Auto-Submitted", "auto-replied (vacation)");
1176
0
  rfc2822_header_write(msg, "Precedence", "bulk");
1177
1178
  /* Prevent older Microsoft products from replying to this message */
1179
0
  rfc2822_header_write(msg, "X-Auto-Response-Suppress", "All");
1180
1181
0
  rfc2822_header_write(msg, "MIME-Version", "1.0");
1182
1183
0
  if (!actx->mime) {
1184
0
    rfc2822_header_write(msg, "Content-Type",
1185
0
             "text/plain; charset=utf-8");
1186
0
    rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
1187
0
    str_append(msg, "\r\n");
1188
0
  }
1189
1190
0
  str_printfa(msg, "%s\r\n", actx->reason);
1191
0
  o_stream_nsend(output, str_data(msg), str_len(msg));
1192
1193
  /* Close smtp session */
1194
0
  ret = sieve_smtp_finish(sctx, &error);
1195
0
  if (ret <= 0) {
1196
0
    if (ret < 0) {
1197
0
      sieve_result_global_error(
1198
0
        aenv, "failed to send vacation response to %s: "
1199
0
        "<%s> (temporary error)",
1200
0
        smtp_address_encode(smtp_to),
1201
0
        str_sanitize(error, 512));
1202
0
    } else {
1203
0
      sieve_result_global_log_error(
1204
0
        aenv, "failed to send vacation response to %s: "
1205
0
        "<%s> (permanent error)",
1206
0
        smtp_address_encode(smtp_to),
1207
0
        str_sanitize(error, 512));
1208
0
    }
1209
    /* This error will be ignored in the end */
1210
0
    return SIEVE_EXEC_FAILURE;
1211
0
  }
1212
1213
0
  eenv->exec_status->significant_action_executed = TRUE;
1214
0
  return SIEVE_EXEC_OK;
1215
0
}
1216
1217
static void
1218
act_vacation_hash(struct act_vacation_context *vctx, const char *sender,
1219
      unsigned char hash_r[])
1220
0
{
1221
0
  const char *rpath = t_str_lcase(sender);
1222
0
  struct md5_context ctx;
1223
1224
0
  md5_init(&ctx);
1225
0
  md5_update(&ctx, rpath, strlen(rpath));
1226
1227
0
  md5_update(&ctx, vctx->handle, strlen(vctx->handle));
1228
1229
0
  md5_final(&ctx, hash_r);
1230
0
}
1231
1232
static int
1233
act_vacation_commit(const struct sieve_action_exec_env *aenv,
1234
        void *tr_context ATTR_UNUSED)
1235
0
{
1236
0
  const struct sieve_action *action = aenv->action;
1237
0
  const struct sieve_extension *ext = action->ext;
1238
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
1239
0
  struct sieve_instance *svinst = eenv->svinst;
1240
0
  const struct ext_vacation_context *extctx = ext->context;
1241
0
  struct act_vacation_context *actx = action->context;
1242
0
  unsigned char dupl_hash[MD5_RESULTLEN];
1243
0
  struct mail *mail = sieve_message_get_mail(aenv->msgctx);
1244
0
  const struct smtp_address *sender, *recipient;
1245
0
  const struct smtp_address *orig_recipient, *user_email;
1246
0
  const struct smtp_address *smtp_from;
1247
0
  struct message_address reply_from;
1248
0
  const char *const *hdsp, *const *headers;
1249
0
  int ret;
1250
1251
0
  if ((eenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) {
1252
0
    sieve_result_global_log(
1253
0
      aenv, "not sending vacation reply (skipped)");
1254
0
    return SIEVE_EXEC_OK;
1255
0
  }
1256
1257
0
  sender = sieve_message_get_sender(aenv->msgctx);
1258
0
  recipient = sieve_message_get_final_recipient(aenv->msgctx);
1259
1260
0
  i_zero(&reply_from);
1261
0
  smtp_from = orig_recipient = user_email = NULL;
1262
1263
  /* Is the recipient unset?
1264
   */
1265
0
  if (smtp_address_isnull(recipient)) {
1266
0
    sieve_result_global_warning(
1267
0
      aenv, "vacation action aborted: "
1268
0
      "envelope recipient is <>");
1269
0
    return SIEVE_EXEC_OK;
1270
0
  }
1271
1272
  /* Is the return path unset ?
1273
   */
1274
0
  if (smtp_address_isnull(sender)) {
1275
0
    sieve_result_global_log(aenv, "discarded vacation reply to <>");
1276
0
    return SIEVE_EXEC_OK;
1277
0
  }
1278
1279
  /* Are we perhaps trying to respond to ourselves ?
1280
   */
1281
0
  if (smtp_address_equals_icase(sender, recipient)) {
1282
0
    sieve_result_global_log(
1283
0
      aenv, "discarded vacation reply to own address <%s>",
1284
0
      smtp_address_encode(sender));
1285
0
    return SIEVE_EXEC_OK;
1286
0
  }
1287
1288
  /* Are we perhaps trying to respond to one of our alternative :addresses?
1289
   */
1290
0
  if (actx->addresses != NULL) {
1291
0
    const struct smtp_address *const *alt_address;
1292
1293
0
    alt_address = actx->addresses;
1294
0
    while (*alt_address != NULL) {
1295
0
      if (smtp_address_equals_icase(sender, *alt_address)) {
1296
0
        sieve_result_global_log(
1297
0
          aenv,
1298
0
          "discarded vacation reply to own address <%s> "
1299
0
          "(as specified using :addresses argument)",
1300
0
          smtp_address_encode(sender));
1301
0
        return SIEVE_EXEC_OK;
1302
0
      }
1303
0
      alt_address++;
1304
0
    }
1305
0
  }
1306
1307
  /* Did whe respond to this user before? */
1308
0
  if (sieve_action_duplicate_check_available(aenv)) {
1309
0
    bool duplicate;
1310
1311
0
    act_vacation_hash(actx, smtp_address_encode(sender), dupl_hash);
1312
1313
0
    ret = sieve_action_duplicate_check(aenv, dupl_hash,
1314
0
               sizeof(dupl_hash),
1315
0
               &duplicate);
1316
0
    if (ret < SIEVE_EXEC_OK) {
1317
0
      sieve_result_critical(
1318
0
        aenv, "failed to check for duplicate vacation response",
1319
0
        "failed to check for duplicate vacation response%s",
1320
0
        (ret == SIEVE_EXEC_TEMP_FAILURE ?
1321
0
         " (temporaty failure)" : ""));
1322
0
      return ret;
1323
0
    }
1324
0
    if (duplicate) {
1325
0
      sieve_result_global_log(
1326
0
        aenv,
1327
0
        "discarded duplicate vacation response to <%s>",
1328
0
        smtp_address_encode(sender));
1329
0
      return SIEVE_EXEC_OK;
1330
0
    }
1331
0
  }
1332
1333
  /* Are we trying to respond to a mailing list ? */
1334
0
  hdsp = _list_headers;
1335
0
  while (*hdsp != NULL) {
1336
0
    ret = mail_get_headers(mail, *hdsp, &headers);
1337
0
    if (ret < 0) {
1338
0
      return sieve_result_mail_error(
1339
0
        aenv, mail,
1340
0
        "failed to read header field '%s'", *hdsp);
1341
0
    }
1342
1343
0
    if (ret > 0 && headers[0] != NULL) {
1344
      /* Yes, bail out */
1345
0
      sieve_result_global_log(
1346
0
        aenv, "discarding vacation response "
1347
0
        "to mailinglist recipient <%s>",
1348
0
        smtp_address_encode(sender));
1349
0
      return SIEVE_EXEC_OK;
1350
0
    }
1351
0
    hdsp++;
1352
0
  }
1353
1354
  /* Is the message that we are replying to an automatic reply ? */
1355
0
  ret = mail_get_headers(mail, "auto-submitted", &headers);
1356
0
  if (ret < 0) {
1357
0
    return sieve_result_mail_error(
1358
0
      aenv, mail,
1359
0
      "failed to read header field 'auto-submitted'");
1360
0
  }
1361
  /* Theoretically multiple headers could exist, so lets make sure */
1362
0
  if (ret > 0) {
1363
0
    hdsp = headers;
1364
0
    while (*hdsp != NULL) {
1365
0
      if (strcasecmp(*hdsp, "no") != 0) {
1366
0
        sieve_result_global_log(
1367
0
          aenv, "discarding vacation response "
1368
0
          "to auto-submitted message from <%s>",
1369
0
          smtp_address_encode(sender));
1370
0
          return SIEVE_EXEC_OK;
1371
0
      }
1372
0
      hdsp++;
1373
0
    }
1374
0
  }
1375
1376
  /* Check for the (non-standard) precedence header */
1377
0
  ret = mail_get_headers(mail, "precedence", &headers);
1378
0
  if (ret < 0) {
1379
0
    return sieve_result_mail_error(
1380
0
      aenv, mail, "failed to read header field 'precedence'");
1381
0
  }
1382
  /* Theoretically multiple headers could exist, so lets make sure */
1383
0
  if (ret > 0) {
1384
0
    hdsp = headers;
1385
0
    while (*hdsp != NULL) {
1386
0
      if (strcasecmp(*hdsp, "junk") == 0 ||
1387
0
          strcasecmp(*hdsp, "bulk") == 0 ||
1388
0
          strcasecmp(*hdsp, "list") == 0) {
1389
0
        sieve_result_global_log(
1390
0
          aenv, "discarding vacation response "
1391
0
          "to precedence=%s message from <%s>",
1392
0
          *hdsp, smtp_address_encode(sender));
1393
0
          return SIEVE_EXEC_OK;
1394
0
      }
1395
0
      hdsp++;
1396
0
    }
1397
0
  }
1398
1399
  /* Check for the (non-standard) Microsoft X-Auto-Response-Suppress header */
1400
0
  ret = mail_get_headers(mail, "x-auto-response-suppress", &headers);
1401
0
  if (ret < 0) {
1402
0
    return sieve_result_mail_error(
1403
0
      aenv, mail,
1404
0
      "failed to read header field 'x-auto-response-suppress'");
1405
0
  }
1406
  /* Theoretically multiple headers could exist, so lets make sure */
1407
0
  if (ret > 0) {
1408
0
    hdsp = headers;
1409
0
    while (*hdsp != NULL) {
1410
0
      const char *const *flags = t_strsplit(*hdsp, ",");
1411
1412
0
      while (*flags != NULL) {
1413
0
        const char *flag = t_str_trim(*flags, " \t");
1414
1415
0
        if (strcasecmp(flag, "All") == 0 ||
1416
0
            strcasecmp(flag, "OOF") == 0) {
1417
0
          sieve_result_global_log(
1418
0
            aenv, "discarding vacation response to message from <%s> "
1419
0
            "('%s' flag found in x-auto-response-suppress header)",
1420
0
            smtp_address_encode(sender), flag);
1421
0
          return SIEVE_EXEC_OK;
1422
0
        }
1423
0
        flags++;
1424
0
      }
1425
0
      hdsp++;
1426
0
    }
1427
0
  }
1428
1429
  /* Do not reply to system addresses */
1430
0
  if (_is_system_address(sender)) {
1431
0
    sieve_result_global_log(
1432
0
      aenv, "not sending vacation response to system address <%s>",
1433
0
      smtp_address_encode(sender));
1434
0
    return SIEVE_EXEC_OK;
1435
0
  }
1436
1437
  /* Fetch original recipient if necessary */
1438
0
  if (extctx->set->use_original_recipient)
1439
0
    orig_recipient = sieve_message_get_orig_recipient(aenv->msgctx);
1440
  /* Fetch explicitly configured user email address */
1441
0
  if (svinst->set->parsed.user_email != NULL)
1442
0
    user_email = svinst->set->parsed.user_email;
1443
1444
  /* Is the original message directly addressed to the user or the addresses
1445
   * specified using the :addresses tag?
1446
   */
1447
0
  hdsp = _my_address_headers;
1448
0
  while (*hdsp != NULL) {
1449
0
    ret = mail_get_headers(mail, *hdsp, &headers);
1450
0
    if (ret < 0) {
1451
0
      return sieve_result_mail_error(
1452
0
        aenv, mail, "failed to read header field '%s'",
1453
0
        *hdsp);
1454
0
    }
1455
0
    if (ret > 0 && headers[0] != NULL) {
1456
      /* Final recipient directly listed in headers? */
1457
0
      if (_contains_my_address(headers, recipient)) {
1458
0
        smtp_from = recipient;
1459
0
        message_address_init_from_smtp(
1460
0
          &reply_from, NULL, recipient);
1461
0
        break;
1462
0
      }
1463
1464
      /* Original recipient directly listed in headers? */
1465
0
      if (!smtp_address_isnull(orig_recipient) &&
1466
0
          _contains_my_address(headers, orig_recipient)) {
1467
0
        smtp_from = orig_recipient;
1468
0
        message_address_init_from_smtp(
1469
0
          &reply_from, NULL, orig_recipient);
1470
0
        break;
1471
0
      }
1472
1473
      /* User-provided :addresses listed in headers? */
1474
0
      if (actx->addresses != NULL) {
1475
0
        bool found = FALSE;
1476
0
        const struct smtp_address *const *my_address;
1477
1478
0
        my_address = actx->addresses;
1479
0
        while (!found && *my_address != NULL) {
1480
0
          if ((found = _contains_my_address(headers, *my_address))) {
1481
            /* Avoid letting user determine SMTP sender directly */
1482
0
            smtp_from = (orig_recipient == NULL ?
1483
0
                   recipient : orig_recipient);
1484
0
            message_address_init_from_smtp(
1485
0
              &reply_from, NULL, *my_address);
1486
0
          }
1487
0
          my_address++;
1488
0
        }
1489
1490
0
        if (found) break;
1491
0
      }
1492
1493
      /* Explicitly-configured user email address directly listed in
1494
         headers? */
1495
0
      if (user_email != NULL &&
1496
0
          _contains_my_address(headers, user_email)) {
1497
0
        smtp_from = user_email;
1498
0
        message_address_init_from_smtp(
1499
0
          &reply_from, NULL, smtp_from);
1500
0
        break;
1501
0
      }
1502
0
    }
1503
0
    hdsp++;
1504
0
  }
1505
1506
  /* My address not found in the headers; we got an implicit delivery */
1507
0
  if (*hdsp == NULL) {
1508
0
    if (!extctx->set->check_recipient) {
1509
      /* Send reply from envelope recipient address */
1510
0
      smtp_from = (orig_recipient == NULL ?
1511
0
             recipient : orig_recipient);
1512
0
      if (user_email == NULL)
1513
0
        user_email = sieve_get_user_email(svinst);
1514
0
      message_address_init_from_smtp(&reply_from,
1515
0
                   NULL, user_email);
1516
0
    } else {
1517
0
      const char *orig_rcpt_str = "", *user_email_str = "";
1518
1519
      /* Bail out */
1520
0
      if (extctx->set->use_original_recipient) {
1521
0
        orig_rcpt_str =
1522
0
          t_strdup_printf("original-recipient=<%s>, ",
1523
0
              (orig_recipient == NULL ? "UNAVAILABLE" :
1524
0
               smtp_address_encode(orig_recipient)));
1525
0
      }
1526
1527
0
      if (user_email != NULL) {
1528
0
        user_email_str = t_strdup_printf(
1529
0
          "user-email=<%s>, ",
1530
0
          smtp_address_encode(user_email));
1531
0
      }
1532
1533
0
      sieve_result_global_log(
1534
0
        aenv, "discarding vacation response for implicitly delivered message; "
1535
0
        "no known (envelope) recipient address found in message headers "
1536
0
        "(recipient=<%s>, %s%sand%s additional ':addresses' are specified)",
1537
0
        smtp_address_encode(recipient),
1538
0
        orig_rcpt_str, user_email_str,
1539
0
        (actx->addresses == NULL || *actx->addresses == NULL ?
1540
0
         " no" : ""));
1541
0
      return SIEVE_EXEC_OK;
1542
0
    }
1543
0
  }
1544
1545
  /* Send the message */
1546
1547
0
  T_BEGIN {
1548
0
    ret = act_vacation_send(
1549
0
      aenv, extctx, actx, sender,
1550
0
      (extctx->set->send_from_recipient ? smtp_from : NULL),
1551
0
      &reply_from);
1552
0
  } T_END;
1553
1554
0
  if (ret == SIEVE_EXEC_OK) {
1555
0
    sieve_number_t seconds;
1556
1557
0
    eenv->exec_status->significant_action_executed = TRUE;
1558
1559
0
    struct event_passthrough *e =
1560
0
      sieve_action_create_finish_event(aenv);
1561
1562
0
    sieve_result_event_log(aenv, e->event(),
1563
0
               "sent vacation response to <%s>",
1564
0
               smtp_address_encode(sender));
1565
1566
    /* Check period limits once more */
1567
0
    seconds = actx->seconds;
1568
0
    if (seconds < extctx->set->min_period)
1569
0
      seconds = extctx->set->min_period;
1570
0
    else if (extctx->set->max_period > 0 &&
1571
0
       seconds > extctx->set->max_period)
1572
0
      seconds = extctx->set->max_period;
1573
1574
    /* Mark as replied */
1575
0
    if (seconds > 0) {
1576
0
      sieve_action_duplicate_mark(aenv, dupl_hash,
1577
0
                sizeof(dupl_hash),
1578
0
                ioloop_time + seconds);
1579
0
    }
1580
0
  }
1581
1582
0
  if (ret == SIEVE_EXEC_TEMP_FAILURE)
1583
0
    return SIEVE_EXEC_TEMP_FAILURE;
1584
1585
  /* Ignore all other errors */
1586
0
  return SIEVE_EXEC_OK;
1587
0
}