Coverage Report

Created: 2026-04-12 07:00

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