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/duplicate/ext-duplicate-common.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "md5.h"
5
#include "ioloop.h"
6
#include "str.h"
7
#include "str-sanitize.h"
8
#include "array.h"
9
#include "settings.h"
10
11
#include "sieve-common.h"
12
#include "sieve-error.h"
13
#include "sieve-extensions.h"
14
#include "sieve-message.h"
15
#include "sieve-code.h"
16
#include "sieve-runtime.h"
17
#include "sieve-interpreter.h"
18
#include "sieve-actions.h"
19
#include "sieve-result.h"
20
21
#include "ext-duplicate-settings.h"
22
#include "ext-duplicate-common.h"
23
24
/*
25
 * Extension configuration
26
 */
27
28
int ext_duplicate_load(const struct sieve_extension *ext, void **context_r)
29
0
{
30
0
  struct sieve_instance *svinst = ext->svinst;
31
0
  const struct ext_duplicate_settings *set;
32
0
  struct ext_duplicate_context *extctx;
33
0
  const char *error;
34
35
0
  if (settings_get(svinst->event, &ext_duplicate_setting_parser_info, 0,
36
0
       &set, &error) < 0) {
37
0
    e_error(svinst->event, "%s", error);
38
0
    return -1;
39
0
  }
40
41
0
  extctx = i_new(struct ext_duplicate_context, 1);
42
0
  extctx->set = set;
43
44
0
  *context_r = extctx;
45
0
  return 0;
46
0
}
47
48
void ext_duplicate_unload(const struct sieve_extension *ext)
49
0
{
50
0
  struct ext_duplicate_context *extctx = ext->context;
51
52
0
  if (extctx == NULL)
53
0
    return;
54
0
  settings_free(extctx->set);
55
0
  i_free(extctx);
56
0
}
57
58
/*
59
 * Duplicate_mark action
60
 */
61
62
struct act_duplicate_mark_data {
63
  const char *handle;
64
  unsigned int period;
65
  unsigned char hash[MD5_RESULTLEN];
66
  bool last:1;
67
};
68
69
static void
70
act_duplicate_mark_print(const struct sieve_action *action,
71
       const struct sieve_result_print_env *rpenv,
72
       bool *keep);
73
static void
74
act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
75
        void *tr_context, int status);
76
77
static const struct sieve_action_def act_duplicate_mark = {
78
  .name = "duplicate_mark",
79
  .print = act_duplicate_mark_print,
80
  .finish = act_duplicate_mark_finish,
81
};
82
83
static void
84
act_duplicate_mark_print(const struct sieve_action *action,
85
       const struct sieve_result_print_env *rpenv,
86
       bool *keep ATTR_UNUSED)
87
0
{
88
0
  struct act_duplicate_mark_data *data =
89
0
    (struct act_duplicate_mark_data *)action->context;
90
0
  const char *last = (data->last ? " last" : "");
91
92
0
  if (data->handle != NULL) {
93
0
    sieve_result_action_printf(
94
0
      rpenv, "track%s duplicate with handle: %s",
95
0
      last, str_sanitize(data->handle, 128));
96
0
  } else {
97
0
    sieve_result_action_printf(rpenv, "track%s duplicate", last);
98
0
  }
99
0
}
100
101
static void
102
act_duplicate_mark_finish(const struct sieve_action_exec_env *aenv,
103
        void *tr_context ATTR_UNUSED, int status)
104
0
{
105
0
  const struct sieve_execute_env *eenv = aenv->exec_env;
106
0
  struct act_duplicate_mark_data *data =
107
0
    (struct act_duplicate_mark_data *)aenv->action->context;
108
109
0
  if (status != SIEVE_EXEC_OK) {
110
0
    e_debug(aenv->event, "Not marking duplicate (status=%s)",
111
0
      sieve_execution_exitcode_to_str(status));
112
0
    return;
113
0
  }
114
115
0
  e_debug(aenv->event, "Marking duplicate");
116
117
  /* Message was handled successfully, so track duplicate for this
118
   * message.
119
   */
120
0
  eenv->exec_status->significant_action_executed = TRUE;
121
0
  sieve_action_duplicate_mark(aenv, data->hash, sizeof(data->hash),
122
0
            ioloop_time + data->period);
123
0
}
124
125
/*
126
 * Duplicate checking
127
 */
128
129
struct ext_duplicate_handle {
130
  const char *handle;
131
  bool last:1;
132
  bool duplicate:1;
133
};
134
135
struct ext_duplicate_hash {
136
  unsigned char hash[MD5_RESULTLEN];
137
  ARRAY(struct ext_duplicate_handle) handles;
138
};
139
140
struct ext_duplicate_runtime_context {
141
  ARRAY(struct ext_duplicate_hash) hashes;
142
};
143
144
static void
145
ext_duplicate_hash(string_t *handle, const char *value, size_t value_len,
146
       bool last, unsigned char hash_r[])
147
0
{
148
0
  static const char *id = "sieve duplicate";
149
0
  struct md5_context md5ctx;
150
151
0
  md5_init(&md5ctx);
152
0
  md5_update(&md5ctx, id, strlen(id));
153
0
  if (last)
154
0
    md5_update(&md5ctx, "0", 1);
155
0
  else
156
0
    md5_update(&md5ctx, "+", 1);
157
0
  if (handle != NULL) {
158
0
    md5_update(&md5ctx, "h-", 2);
159
0
    md5_update(&md5ctx, str_c(handle), str_len(handle));
160
0
  } else {
161
0
    md5_update(&md5ctx, "default", 7);
162
0
  }
163
0
  md5_update(&md5ctx, value, value_len);
164
0
  md5_final(&md5ctx, hash_r);
165
0
}
166
167
int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
168
      const char *value, size_t value_len,
169
      sieve_number_t period, bool last, bool *duplicate_r)
170
0
{
171
0
  const struct sieve_execute_env *eenv = renv->exec_env;
172
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
173
0
  struct ext_duplicate_runtime_context *rctx;
174
0
  bool duplicate = FALSE;
175
0
  pool_t msg_pool = NULL, result_pool = NULL;
176
0
  unsigned char hash[MD5_RESULTLEN];
177
0
  struct ext_duplicate_hash *hash_record = NULL;
178
0
  struct ext_duplicate_handle *handle_record = NULL;
179
0
  struct act_duplicate_mark_data *act;
180
0
  int ret;
181
182
0
  *duplicate_r = FALSE;
183
184
0
  if (!sieve_execute_duplicate_check_available(eenv)) {
185
0
    sieve_runtime_warning(
186
0
      renv, NULL, "duplicate test: "
187
0
      "duplicate checking not available in this context");
188
0
    return SIEVE_EXEC_OK;
189
0
  }
190
191
0
  if (value == NULL)
192
0
    return SIEVE_EXEC_OK;
193
194
  /* Create hash */
195
0
  ext_duplicate_hash(handle, value, value_len, last, hash);
196
197
  /* Get context; find out whether duplicate was checked earlier */
198
0
  rctx = sieve_message_context_extension_get(renv->msgctx, this_ext);
199
200
0
  if (rctx == NULL) {
201
    /* Create context */
202
0
    msg_pool = sieve_message_context_pool(renv->msgctx);
203
0
    rctx = p_new(msg_pool, struct ext_duplicate_runtime_context, 1);
204
0
    sieve_message_context_extension_set(renv->msgctx, this_ext,
205
0
                rctx);
206
0
  } else if (array_is_created(&rctx->hashes)) {
207
0
    struct ext_duplicate_hash *record;
208
209
0
    array_foreach_modifiable(&rctx->hashes, record) {
210
0
      if (memcmp(record->hash, hash, MD5_RESULTLEN) == 0) {
211
0
        hash_record = record;
212
0
        break;
213
0
      }
214
0
    }
215
0
  }
216
0
  if (hash_record != NULL) {
217
0
    const struct ext_duplicate_handle *rhandle;
218
0
    array_foreach(&hash_record->handles, rhandle) {
219
0
      const char *handle_str =
220
0
        (handle == NULL ? NULL : str_c(handle));
221
0
      if (null_strcmp(rhandle->handle, handle_str) == 0 &&
222
0
          rhandle->last == last)
223
0
        return (rhandle->duplicate ?
224
0
                SIEVE_DUPLICATE_CHECK_RESULT_EXISTS :
225
0
          SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND);
226
0
    }
227
0
  }
228
229
0
  result_pool = sieve_result_pool(renv->result);
230
0
  act = p_new(result_pool, struct act_duplicate_mark_data, 1);
231
0
  if (handle != NULL)
232
0
    act->handle = p_strdup(result_pool, str_c(handle));
233
0
  act->period = period;
234
0
  memcpy(act->hash, hash, MD5_RESULTLEN);
235
0
  act->last = last;
236
237
  /* Check duplicate */
238
0
  ret = sieve_execute_duplicate_check(eenv, hash, sizeof(hash),
239
0
              &duplicate);
240
0
  if (ret >= SIEVE_EXEC_OK && !duplicate && last) {
241
0
    unsigned char no_last_hash[MD5_RESULTLEN];
242
243
    /* Check for entry without :last */
244
0
    ext_duplicate_hash(handle, value, value_len,
245
0
           FALSE, no_last_hash);
246
0
    ret = sieve_execute_duplicate_check(
247
0
      eenv, no_last_hash, sizeof(no_last_hash),
248
0
      &duplicate);
249
0
  }
250
0
  if (ret < SIEVE_EXEC_OK) {
251
0
    sieve_runtime_critical(
252
0
      renv, NULL, "failed to check for duplicate",
253
0
      "failed to check for duplicate%s",
254
0
      (ret == SIEVE_EXEC_TEMP_FAILURE ?
255
0
       " (temporary failure)" : ""));
256
0
    return ret;
257
0
  }
258
259
  /* We may only mark the message as duplicate when Sieve script executes
260
     successfully; therefore defer this operation until successful result
261
     execution.
262
   */
263
0
  if (!duplicate || last) {
264
0
    if (sieve_result_add_action(renv, NULL, NULL,
265
0
              &act_duplicate_mark,
266
0
              NULL, act, 0, FALSE) < 0)
267
0
      return SIEVE_EXEC_FAILURE;
268
0
  }
269
270
  /* Cache result */
271
0
  if (msg_pool == NULL)
272
0
    msg_pool = sieve_message_context_pool(renv->msgctx);
273
0
  if (hash_record == NULL) {
274
0
    if (!array_is_created(&rctx->hashes))
275
0
      p_array_init(&rctx->hashes, msg_pool, 64);
276
0
    hash_record = array_append_space(&rctx->hashes);
277
0
    memcpy(hash_record->hash, hash, MD5_RESULTLEN);
278
0
    p_array_init(&hash_record->handles, msg_pool, 64);
279
0
  }
280
281
0
  handle_record = array_append_space(&hash_record->handles);
282
0
  if (handle != NULL)
283
0
    handle_record->handle = p_strdup(msg_pool, str_c(handle));
284
0
  handle_record->last = last;
285
0
  handle_record->duplicate = duplicate;
286
287
0
  *duplicate_r = duplicate;
288
289
0
  return SIEVE_EXEC_OK;
290
0
}