Coverage Report

Created: 2026-06-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/sieve-binary-file.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 "str-sanitize.h"
7
#include "mempool.h"
8
#include "buffer.h"
9
#include "hash.h"
10
#include "array.h"
11
#include "ostream.h"
12
#include "eacces-error.h"
13
#include "safe-mkstemp.h"
14
#include "file-lock.h"
15
16
#include "sieve-common.h"
17
#include "sieve-error.h"
18
#include "sieve-extensions.h"
19
#include "sieve-code.h"
20
#include "sieve-script.h"
21
22
#include "sieve-binary-private.h"
23
24
#include <sys/types.h>
25
#include <sys/stat.h>
26
#include <unistd.h>
27
#include <fcntl.h>
28
29
/*
30
 * Macros
31
 */
32
33
0
#define SIEVE_BINARY_MAGIC                      0xcafebabe
34
0
#define SIEVE_BINARY_MAGIC_OTHER_ENDIAN         0xbebafeca
35
36
#define SIEVE_BINARY_ALIGN(offset) \
37
0
  (((offset) + 3) & ~3U)
38
#define SIEVE_BINARY_ALIGN_PTR(ptr) \
39
  ((void *)SIEVE_BINARY_ALIGN(((size_t) ptr)))
40
41
0
#define SIEVE_BINARY_PRE_HDR_SIZE_MAJOR         1
42
0
#define SIEVE_BINARY_PRE_HDR_SIZE_MINOR         4
43
0
#define SIEVE_BINARY_PRE_HDR_SIZE_HDR_SIZE      12
44
45
/*
46
 * Header and record structures of the binary on disk
47
 */
48
49
struct sieve_binary_block_index {
50
  uint32_t id;
51
  uint32_t size;
52
  uint32_t offset;
53
  uint32_t ext_id;
54
};
55
56
struct sieve_binary_block_header {
57
  uint32_t id;
58
  uint32_t size;
59
};
60
61
/*
62
 * Utility
63
 */
64
65
static bool sieve_binary_can_update(struct sieve_binary *sbin)
66
0
{
67
0
  const char *dirpath, *p;
68
69
0
  p = strrchr(sbin->path, '/');
70
0
  if (p == NULL)
71
0
    dirpath = ".";
72
0
  else
73
0
    dirpath = t_strdup_until(sbin->path, p);
74
75
0
  return (access(dirpath, W_OK | X_OK) == 0);
76
0
}
77
78
/*
79
 * Header manipulation
80
 */
81
82
static int
83
sieve_binary_file_read_header(struct sieve_binary *sbin, int fd,
84
            struct sieve_binary_header *header_r,
85
            enum sieve_error *error_code_r)
86
0
{
87
0
  struct sieve_binary_header header;
88
0
  ssize_t rret;
89
90
0
  sieve_error_args_init(&error_code_r, NULL);
91
92
0
  rret = pread(fd, &header, sizeof(header), 0);
93
0
  if (rret == 0) {
94
0
    e_error(sbin->event, "read: "
95
0
      "file is not large enough to contain the header");
96
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
97
0
    return -1;
98
0
  } else if (rret < 0) {
99
0
    e_error(sbin->event, "read: "
100
0
      "failed to read from binary: %m");
101
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
102
0
    return -1;
103
0
  } else if (rret != sizeof(header)) {
104
0
    e_error(sbin->event, "read: "
105
0
      "header read only partially %zd/%zu",
106
0
      rret, sizeof(header));
107
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
108
0
    return -1;
109
0
  }
110
111
  /* Check header validity */
112
0
  if (header.magic != SIEVE_BINARY_MAGIC) {
113
0
    if (header.magic != SIEVE_BINARY_MAGIC_OTHER_ENDIAN) {
114
0
      e_error(sbin->event, "read: "
115
0
        "binary has corrupted header "
116
0
        "(0x%08x) or it is not a Sieve binary",
117
0
        header.magic);
118
0
    } else {
119
0
      e_error(sbin->event, "read: "
120
0
        "binary stored with in different endian format "
121
0
        "(automatically fixed when re-compiled)");
122
0
    }
123
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
124
0
    return -1;
125
0
  }
126
  /* Check binary version */
127
0
  if (header.version_major == SIEVE_BINARY_PRE_HDR_SIZE_MAJOR &&
128
0
      header.version_minor == SIEVE_BINARY_PRE_HDR_SIZE_MINOR) {
129
    /* Old header without hdr_size; clear new fields */
130
0
    static const size_t old_header_size =
131
0
      SIEVE_BINARY_PRE_HDR_SIZE_HDR_SIZE;
132
0
    memset(PTR_OFFSET(&header, old_header_size), 0,
133
0
           (sizeof(header) - old_header_size));
134
0
    header.hdr_size = old_header_size;
135
0
  } else if (header.version_major != SIEVE_BINARY_VERSION_MAJOR) {
136
    /* Binary is of different major version. Caller will have to
137
       recompile */
138
0
    bool important = (sbin->script == NULL ||
139
0
          !sieve_binary_can_update(sbin));
140
0
    enum log_type log_type = (important ?
141
0
            LOG_TYPE_ERROR : LOG_TYPE_DEBUG);
142
0
    e_log(sbin->event, log_type, "read: "
143
0
          "binary stored with different major version %d.%d "
144
0
          "(!= %d.%d; automatically fixed when re-compiled)",
145
0
          (int)header.version_major, (int)header.version_minor,
146
0
          SIEVE_BINARY_VERSION_MAJOR, SIEVE_BINARY_VERSION_MINOR);
147
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
148
0
    return -1;
149
0
  } else if (header.hdr_size < SIEVE_BINARY_BASE_HEADER_SIZE) {
150
    /* Header size is smaller than base size */
151
0
    e_error(sbin->event, "read: "
152
0
      "binary is corrupt: header size is too small");
153
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
154
0
    return -1;
155
0
  }
156
  /* Check block content */
157
0
  if (header.blocks == 0) {
158
0
    e_error(sbin->event, "read: "
159
0
      "binary is corrupt: it contains no blocks");
160
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
161
0
    return -1;
162
0
  }
163
  /* Valid */
164
0
  *header_r = header;
165
0
  return 0;
166
0
}
167
168
static int
169
sieve_binary_file_write_header(struct sieve_binary *sbin, int fd,
170
             struct sieve_binary_header *header,
171
             enum sieve_error *error_code_r)
172
0
{
173
0
  ssize_t wret;
174
175
0
  wret = pwrite(fd, header, sizeof(*header), 0);
176
0
  if (wret < 0) {
177
0
    e_error(sbin->event, "update: "
178
0
      "failed to write to binary: %m");
179
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
180
0
    return -1;
181
0
  } else if (wret != sizeof(*header)) {
182
0
    e_error(sbin->event, "update: "
183
0
      "header written partially %zd/%zu",
184
0
      wret, sizeof(*header));
185
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
186
0
    return -1;
187
0
  }
188
0
  return 0;
189
0
}
190
191
static void sieve_binary_file_update_header(struct sieve_binary *sbin)
192
0
{
193
0
  struct sieve_binary_header *header = &sbin->header;
194
0
  struct sieve_resource_usage rusage;
195
196
0
  sieve_binary_get_resource_usage(sbin, &rusage);
197
198
0
  i_zero(&header->resource_usage);
199
0
  if (HAS_ALL_BITS(header->flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) ||
200
0
      sieve_resource_usage_is_high(sbin->svinst, &rusage)) {
201
0
    header->resource_usage.update_time = ioloop_time;
202
0
    header->resource_usage.cpu_time_msecs = rusage.cpu_time_msecs;
203
0
  }
204
205
0
  sieve_resource_usage_init(&sbin->rusage);
206
0
  sbin->rusage_updated = FALSE;
207
208
0
  (void)sieve_binary_check_resource_usage(sbin);
209
0
}
210
211
/*
212
 * Saving the binary to a file.
213
 */
214
215
static inline bool
216
_save_skip(struct sieve_binary *sbin, struct ostream *stream, size_t size)
217
0
{
218
0
  if ((o_stream_seek(stream, stream->offset + size)) <= 0) {
219
0
    e_error(sbin->event, "save: "
220
0
      "failed to skip output stream to position "
221
0
      "%"PRIuUOFF_T": %s", stream->offset + size,
222
0
      strerror(stream->stream_errno));
223
0
    return FALSE;
224
0
  }
225
226
0
  return TRUE;
227
0
}
228
229
static inline bool
230
_save_skip_aligned(struct sieve_binary *sbin, struct ostream *stream,
231
       size_t size, uoff_t *offset)
232
0
{
233
0
  uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
234
235
0
  if ((o_stream_seek(stream, aligned_offset + size)) <= 0) {
236
0
    e_error(sbin->event, "save: "
237
0
      "failed to skip output stream to position "
238
0
      "%"PRIuUOFF_T": %s", aligned_offset + size,
239
0
      strerror(stream->stream_errno));
240
0
    return FALSE;
241
0
  }
242
243
0
  if (offset != NULL)
244
0
    *offset = aligned_offset;
245
0
  return TRUE;
246
0
}
247
248
/* FIXME: Is this even necessary for a file? */
249
static bool
250
_save_full(struct sieve_binary *sbin, struct ostream *stream,
251
     const void *data, size_t size)
252
0
{
253
0
  size_t bytes_left = size;
254
0
  const void *pdata = data;
255
256
0
  while (bytes_left > 0) {
257
0
    ssize_t ret;
258
259
0
    ret = o_stream_send(stream, pdata, bytes_left);
260
0
    if (ret <= 0) {
261
0
      e_error(sbin->event, "save: "
262
0
        "failed to write %zu bytes "
263
0
        "to output stream: %s", bytes_left,
264
0
        strerror(stream->stream_errno));
265
0
      return FALSE;
266
0
    }
267
268
0
    pdata = PTR_OFFSET(pdata, ret);
269
0
    bytes_left -= ret;
270
0
  }
271
272
0
  return TRUE;
273
0
}
274
275
static bool
276
_save_aligned(struct sieve_binary *sbin, struct ostream *stream,
277
        const void *data, size_t size, uoff_t *offset)
278
0
{
279
0
  uoff_t aligned_offset = SIEVE_BINARY_ALIGN(stream->offset);
280
281
0
  o_stream_cork(stream);
282
283
  /* Align the data by adding zeroes to the output stream */
284
0
  if (stream->offset < aligned_offset) {
285
0
    if (!_save_skip(sbin, stream,
286
0
        (aligned_offset - stream->offset)))
287
0
      return FALSE;
288
0
  }
289
290
0
  if (!_save_full(sbin, stream, data, size))
291
0
    return FALSE;
292
293
0
  o_stream_uncork(stream);
294
295
0
  if (offset != NULL)
296
0
    *offset = aligned_offset;
297
0
  return TRUE;
298
0
}
299
300
static bool
301
_save_block(struct sieve_binary *sbin, struct ostream *stream, unsigned int id)
302
0
{
303
0
  struct sieve_binary_block_header block_header;
304
0
  struct sieve_binary_block *block;
305
0
  const void *data;
306
0
  size_t size;
307
308
0
  block = sieve_binary_block_get(sbin, id);
309
0
  if (block == NULL)
310
0
    return FALSE;
311
312
0
  data = buffer_get_data(block->data, &size);
313
314
0
  block_header.id = id;
315
0
  block_header.size = size;
316
317
0
  if (!_save_aligned(sbin, stream, &block_header, sizeof(block_header),
318
0
         &block->offset))
319
0
    return FALSE;
320
321
0
  return _save_aligned(sbin, stream, data, size, NULL);
322
0
}
323
324
static bool
325
_save_block_index_record(struct sieve_binary *sbin, struct ostream *stream,
326
       unsigned int id)
327
0
{
328
0
  struct sieve_binary_block *block;
329
0
  struct sieve_binary_block_index header;
330
331
0
  block = sieve_binary_block_get(sbin, id);
332
0
  if (block == NULL)
333
0
    return FALSE;
334
335
0
  header.id = id;
336
0
  header.size = buffer_get_used_size(block->data);
337
0
  header.ext_id = block->ext_index;
338
0
  header.offset = block->offset;
339
340
0
  if (!_save_full(sbin, stream, &header, sizeof(header))) {
341
0
    e_error(sbin->event, "save: "
342
0
      "failed to save block index header %d", id);
343
0
    return FALSE;
344
0
  }
345
346
0
  return TRUE;
347
0
}
348
349
static bool
350
sieve_binary_save_to_stream(struct sieve_binary *sbin, struct ostream *stream)
351
0
{
352
0
  struct sieve_binary_header *header = &sbin->header;
353
0
  struct sieve_binary_block *ext_block;
354
0
  unsigned int ext_count, blk_count, i;
355
0
  uoff_t block_index;
356
357
0
  blk_count = sieve_binary_block_count(sbin);
358
359
  /* Create header */
360
361
0
  header->magic = SIEVE_BINARY_MAGIC;
362
0
  header->version_major = SIEVE_BINARY_VERSION_MAJOR;
363
0
  header->version_minor = SIEVE_BINARY_VERSION_MINOR;
364
0
  header->blocks = blk_count;
365
0
  header->hdr_size = sizeof(*header);
366
367
0
  header->flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
368
0
  sieve_binary_file_update_header(sbin);
369
370
0
  if (!_save_aligned(sbin, stream, header, sizeof(*header), NULL)) {
371
0
    e_error(sbin->event, "save: failed to save header");
372
0
    return FALSE;
373
0
  }
374
375
  /* Skip block index for now */
376
377
0
  if (!_save_skip_aligned(
378
0
    sbin, stream,
379
0
    (sizeof(struct sieve_binary_block_index) * blk_count),
380
0
    &block_index))
381
0
    return FALSE;
382
383
  /* Create block containing all used extensions */
384
385
0
  ext_block = sieve_binary_block_get(sbin, SBIN_SYSBLOCK_EXTENSIONS);
386
0
  i_assert(ext_block != NULL);
387
0
  sieve_binary_block_clear(ext_block);
388
389
0
  ext_count = array_count(&sbin->linked_extensions);
390
0
  sieve_binary_emit_unsigned(ext_block, ext_count);
391
392
0
  for (i = 0; i < ext_count; i++) {
393
0
    struct sieve_binary_extension_reg *const *ext =
394
0
      array_idx(&sbin->linked_extensions, i);
395
396
0
    sieve_binary_emit_cstring(
397
0
      ext_block, sieve_extension_name((*ext)->extension));
398
0
    sieve_binary_emit_unsigned(
399
0
      ext_block, sieve_extension_version((*ext)->extension));
400
0
    sieve_binary_emit_unsigned(ext_block, (*ext)->block_id);
401
0
  }
402
403
  /* Save all blocks into the binary */
404
405
0
  for (i = 0; i < blk_count; i++) {
406
0
    if (!_save_block(sbin, stream, i))
407
0
      return FALSE;
408
0
  }
409
410
  /* Create the block index */
411
0
  o_stream_seek(stream, block_index);
412
0
  for (i = 0; i < blk_count; i++) {
413
0
    if (!_save_block_index_record(sbin, stream, i))
414
0
      return FALSE;
415
0
  }
416
417
0
  if (o_stream_finish(stream) <= 0) {
418
0
    e_error(sbin->event, "save: "
419
0
      "failed to finish output stream: %s",
420
0
      o_stream_get_error(stream));
421
0
    return FALSE;
422
0
  }
423
0
  return TRUE;
424
0
}
425
426
static int
427
sieve_binary_do_save(struct sieve_binary *sbin, const char *path, bool update,
428
         mode_t save_mode, enum sieve_error *error_code_r)
429
0
{
430
0
  int result, fd;
431
0
  string_t *temp_path;
432
0
  struct ostream *stream;
433
0
  struct sieve_binary_extension_reg *const *regs;
434
0
  unsigned int ext_count, i;
435
436
0
  sieve_error_args_init(&error_code_r, NULL);
437
438
  /* Check whether saving is necessary */
439
0
  if (!update && sbin->path != NULL && strcmp(sbin->path, path) == 0) {
440
0
    e_debug(sbin->event, "save: "
441
0
      "not saving binary, because it is already stored");
442
0
    return 0;
443
0
  }
444
445
  /* Open it as temp file first, as not to overwrite an existing just yet */
446
0
  temp_path = t_str_new(256);
447
0
  str_append(temp_path, path);
448
0
  str_append_c(temp_path, '.');
449
0
  fd = safe_mkstemp_hostpid(temp_path, save_mode, (uid_t)-1, (gid_t)-1);
450
0
  if (fd < 0) {
451
0
    if (errno == EACCES) {
452
0
      e_error(sbin->event, "save: "
453
0
        "failed to create temporary file: %s",
454
0
        eacces_error_get_creating("open",
455
0
                str_c(temp_path)));
456
0
      *error_code_r = SIEVE_ERROR_NO_PERMISSION;
457
0
    } else {
458
0
      e_error(sbin->event, "save: "
459
0
        "failed to create temporary file: "
460
0
        "open(%s) failed: %m", str_c(temp_path));
461
0
      *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
462
0
    }
463
0
    return -1;
464
0
  }
465
466
  /* Signal all extensions that we're about to save the binary */
467
0
  regs = array_get(&sbin->extensions, &ext_count);
468
0
  for (i = 0; i < ext_count; i++) {
469
0
    const struct sieve_binary_extension *binext = regs[i]->binext;
470
471
0
    if (binext != NULL && binext->binary_pre_save != NULL &&
472
0
        !binext->binary_pre_save(regs[i]->extension, sbin,
473
0
               regs[i]->context, error_code_r)) {
474
0
      i_assert(*error_code_r != SIEVE_ERROR_NONE);
475
0
      return -1;
476
0
    }
477
0
  }
478
479
  /* Save binary */
480
0
  result = 1;
481
0
  stream = o_stream_create_fd(fd, 0);
482
0
  if (!sieve_binary_save_to_stream(sbin, stream)) {
483
0
    result = -1;
484
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
485
0
    o_stream_ignore_last_errors(stream);
486
0
  }
487
0
  o_stream_destroy(&stream);
488
489
  /* Close saved binary */
490
0
  if (close(fd) < 0) {
491
0
    e_error(sbin->event, "save: "
492
0
      "failed to close temporary file: "
493
0
      "close(fd=%s) failed: %m", str_c(temp_path));
494
0
  }
495
496
  /* Replace any original binary atomically */
497
0
  if (result > 0 && (rename(str_c(temp_path), path) < 0)) {
498
0
    if (errno == EACCES) {
499
0
      e_error(sbin->event, "save: "
500
0
        "failed to save binary: %s",
501
0
        eacces_error_get_creating("rename", path));
502
0
      *error_code_r = SIEVE_ERROR_NO_PERMISSION;
503
0
    } else {
504
0
      e_error(sbin->event, "save: "
505
0
        "failed to save binary: "
506
0
        "rename(%s, %s) failed: %m",
507
0
        str_c(temp_path), path);
508
0
      *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
509
0
    }
510
0
    result = -1;
511
0
  }
512
513
0
  if (result < 0) {
514
    /* Get rid of temp output (if any) */
515
0
    if (unlink(str_c(temp_path)) < 0 && errno != ENOENT) {
516
0
      e_error(sbin->event, "save: "
517
0
        "failed to clean up after error: "
518
0
        "unlink(%s) failed: %m", str_c(temp_path));
519
0
    }
520
0
  } else {
521
0
    if (sbin->path == NULL)
522
0
      sbin->path = p_strdup(sbin->pool, path);
523
524
    /* Signal all extensions that we successfully saved the binary.
525
     */
526
0
    regs = array_get(&sbin->extensions, &ext_count);
527
0
    for (i = 0; i < ext_count; i++) {
528
0
      const struct sieve_binary_extension *binext =
529
0
        regs[i]->binext;
530
531
0
      if (binext != NULL &&
532
0
          binext->binary_post_save != NULL &&
533
0
          !binext->binary_post_save(regs[i]->extension, sbin,
534
0
                  regs[i]->context,
535
0
                  error_code_r)) {
536
0
        i_assert(*error_code_r != SIEVE_ERROR_NONE);
537
0
        result = -1;
538
0
        break;
539
0
      }
540
0
    }
541
542
0
    if (result < 0 && unlink(path) < 0 && errno != ENOENT) {
543
0
      e_error(sbin->event, "failed to clean up after error: "
544
0
        "unlink(%s) failed: %m", path);
545
0
    }
546
0
  }
547
548
0
  return result;
549
0
}
550
551
int sieve_binary_save(struct sieve_binary *sbin, const char *path, bool update,
552
          mode_t save_mode, enum sieve_error *error_code_r)
553
0
{
554
0
  int ret;
555
556
0
  sieve_binary_update_event(sbin, path);
557
0
  ret = sieve_binary_do_save(sbin, path, update, save_mode, error_code_r);
558
0
  sieve_binary_update_event(sbin, NULL);
559
560
0
  return ret;
561
0
}
562
563
564
/*
565
 * Binary file management
566
 */
567
568
static int
569
sieve_binary_fd_open(struct sieve_binary *sbin, const char *path,
570
         int open_flags, enum sieve_error *error_code_r)
571
0
{
572
0
  int fd;
573
574
0
  fd = open(path, open_flags);
575
0
  if (fd < 0) {
576
0
    switch (errno) {
577
0
    case ENOENT:
578
0
      *error_code_r = SIEVE_ERROR_NOT_FOUND;
579
0
      break;
580
0
    case EACCES:
581
0
      e_error(sbin->event, "open: "
582
0
        "failed to open: %s",
583
0
        eacces_error_get("open", path));
584
0
      *error_code_r = SIEVE_ERROR_NO_PERMISSION;
585
0
      break;
586
0
    default:
587
0
      e_error(sbin->event, "open: "
588
0
        "failed to open: open(%s) failed: %m", path);
589
0
      *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
590
0
      break;
591
0
    }
592
0
    return -1;
593
0
  }
594
0
  return fd;
595
0
}
596
597
static int
598
sieve_binary_file_open(struct sieve_binary *sbin, const char *path,
599
           struct sieve_binary_file **file_r,
600
           enum sieve_error *error_code_r)
601
0
{
602
0
  int fd, ret = 0;
603
0
  struct stat st;
604
605
0
  sieve_error_args_init(&error_code_r, NULL);
606
607
0
  fd = sieve_binary_fd_open(sbin, path, O_RDONLY, error_code_r);
608
0
  if (fd < 0)
609
0
    return -1;
610
611
0
  if (fstat(fd, &st) < 0) {
612
0
    if (errno == ENOENT)
613
0
      *error_code_r = SIEVE_ERROR_NOT_FOUND;
614
0
    else {
615
0
      e_error(sbin->event, "open: fstat(%s) failed: %m",
616
0
        path);
617
0
      *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
618
0
    }
619
0
    ret = -1;
620
0
  }
621
622
0
  if (ret == 0 && !S_ISREG(st.st_mode)) {
623
0
    e_error(sbin->event, "open: "
624
0
      "binary is not a regular file");
625
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
626
0
    ret = -1;
627
0
  }
628
629
0
  if (ret < 0) {
630
0
    if (close(fd) < 0) {
631
0
      e_error(sbin->event, "open: "
632
0
        "close() failed after error: %m");
633
0
    }
634
0
    return -1;
635
0
  }
636
637
0
  pool_t pool;
638
0
  struct sieve_binary_file *file;
639
640
0
  pool = pool_alloconly_create("sieve_binary_file", 4096);
641
0
  file = p_new(pool, struct sieve_binary_file, 1);
642
0
  file->pool = pool;
643
0
  file->path = p_strdup(pool, path);
644
0
  file->fd = fd;
645
0
  file->st = st;
646
0
  file->sbin = sbin;
647
648
0
  *file_r = file;
649
0
  return 0;
650
0
}
651
652
void sieve_binary_file_close(struct sieve_binary_file **_file)
653
0
{
654
0
  struct sieve_binary_file *file = *_file;
655
656
0
  *_file = NULL;
657
0
  if (file == NULL)
658
0
    return;
659
660
0
  if (file->fd != -1) {
661
0
    if (close(file->fd) < 0) {
662
0
      e_error(file->sbin->event, "close: "
663
0
        "failed to close: close() failed: %m");
664
0
    }
665
0
  }
666
667
0
  pool_unref(&file->pool);
668
0
}
669
670
static int
671
sieve_binary_file_read(struct sieve_binary_file *file, off_t *offset,
672
           void *buffer, size_t size)
673
0
{
674
0
  struct sieve_binary *sbin = file->sbin;
675
0
  int ret;
676
0
  void *indata = buffer;
677
0
  size_t insize = size;
678
679
0
  *offset = SIEVE_BINARY_ALIGN(*offset);
680
681
  /* Seek to the correct position */
682
0
  if (*offset != file->offset &&
683
0
      lseek(file->fd, *offset, SEEK_SET) == (off_t)-1) {
684
0
    e_error(sbin->event, "read: "
685
0
      "failed to seek(fd, %lld, SEEK_SET): %m",
686
0
      (long long) *offset);
687
0
    return -1;
688
0
  }
689
690
  /* Read record into memory */
691
0
  while (insize > 0) {
692
0
    ret = read(file->fd, indata, insize);
693
0
    if (ret <= 0) {
694
0
      if (ret == 0) {
695
0
        e_error(sbin->event, "read: "
696
0
          "binary is truncated "
697
0
          "(more data expected)");
698
0
      } else {
699
0
        e_error(sbin->event, "read: "
700
0
          "failed to read from binary: %m");
701
0
      }
702
0
      break;
703
0
    }
704
705
0
    indata = PTR_OFFSET(indata, ret);
706
0
    insize -= ret;
707
0
  }
708
709
0
  if (insize != 0) {
710
    /* Failed to read the whole requested record */
711
0
    return 0;
712
0
  }
713
714
0
  *offset += size;
715
0
  file->offset = *offset;
716
0
  return 1;
717
0
}
718
719
static const void *
720
sieve_binary_file_load_data(struct sieve_binary_file *file,
721
          off_t *offset, size_t size)
722
0
{
723
0
  void *data = t_malloc_no0(size);
724
725
0
  if (sieve_binary_file_read(file, offset, data, size) > 0)
726
0
    return data;
727
728
0
  return NULL;
729
0
}
730
731
static buffer_t *
732
sieve_binary_file_load_buffer(struct sieve_binary_file *file,
733
            off_t *offset, size_t size)
734
0
{
735
0
  buffer_t *buffer = buffer_create_dynamic(file->pool, size);
736
737
0
  if (sieve_binary_file_read(file, offset,
738
0
           buffer_get_space_unsafe(buffer, 0, size),
739
0
           size) > 0)
740
0
    return buffer;
741
742
0
  return NULL;
743
0
}
744
745
/*
746
 * Load binary from a file
747
 */
748
749
#define LOAD_HEADER(sbin, offset, header) \
750
0
  (header *)sieve_binary_file_load_data(sbin->file, offset, \
751
0
                sizeof(header))
752
753
bool sieve_binary_load_block(struct sieve_binary_block *sblock)
754
0
{
755
0
  struct sieve_binary *sbin = sblock->sbin;
756
0
  unsigned int id = sblock->id;
757
0
  off_t offset = sblock->offset;
758
0
  const struct sieve_binary_block_header *header =
759
0
    LOAD_HEADER(sbin, &offset,
760
0
          const struct sieve_binary_block_header);
761
762
0
  if (header == NULL) {
763
0
    e_error(sbin->event, "load: binary is corrupt: "
764
0
      "failed to read header of block %d", id);
765
0
    return FALSE;
766
0
  }
767
768
0
  if (header->id != id) {
769
0
    e_error(sbin->event, "load: binary is corrupt: "
770
0
      "header of block %d has non-matching id %d",
771
0
      id, header->id);
772
0
    return FALSE;
773
0
  }
774
775
0
  sblock->data = sieve_binary_file_load_buffer(sbin->file, &offset,
776
0
                 header->size);
777
0
  if (sblock->data == NULL) {
778
0
    e_error(sbin->event, "load: "
779
0
      "failed to read block %d of binary (size=%d)",
780
0
      id, header->size);
781
0
    return FALSE;
782
0
  }
783
784
0
  return TRUE;
785
0
}
786
787
static bool
788
_read_block_index_record(struct sieve_binary *sbin, off_t *offset,
789
       unsigned int id)
790
0
{
791
0
  const struct sieve_binary_block_index *record =
792
0
    LOAD_HEADER(sbin, offset,
793
0
          const struct sieve_binary_block_index);
794
0
  struct sieve_binary_block *block;
795
796
0
  if (record == NULL) {
797
0
    e_error(sbin->event, "open: binary is corrupt: "
798
0
      "failed to load block index record %d", id);
799
0
    return FALSE;
800
0
  }
801
802
0
  if (record->id != id) {
803
0
    e_error(sbin->event, "open: binary is corrupt: "
804
0
      "block index record %d has unexpected id %d",
805
0
      id, record->id);
806
0
    return FALSE;
807
0
  }
808
809
0
  block = sieve_binary_block_create_id(sbin, id);
810
0
  block->ext_index = record->ext_id;
811
0
  block->offset = record->offset;
812
813
0
  return TRUE;
814
0
}
815
816
static int _read_extensions(struct sieve_binary_block *sblock)
817
0
{
818
0
  struct sieve_binary *sbin = sblock->sbin;
819
0
  sieve_size_t offset = 0;
820
0
  unsigned int i, count;
821
0
  int result = 1;
822
823
0
  if (!sieve_binary_read_unsigned(sblock, &offset, &count))
824
0
    return -1;
825
826
0
  for (i = 0; result > 0 && i < count; i++) {
827
0
    T_BEGIN {
828
0
      string_t *extension;
829
0
      const struct sieve_extension *ext;
830
0
      unsigned int version;
831
832
0
      if (sieve_binary_read_string(sblock, &offset,
833
0
                 &extension)) {
834
0
        ext = sieve_extension_get_by_name(
835
0
          sbin->svinst, str_c(extension));
836
837
0
        if (ext == NULL) {
838
0
          e_error(sbin->event, "open: "
839
0
            "binary requires unknown extension '%s'",
840
0
            str_sanitize(str_c(extension), 128));
841
0
          result = 0;
842
0
        } else {
843
0
          struct sieve_binary_extension_reg *ereg = NULL;
844
845
0
          (void)sieve_binary_extension_register(sbin, ext, &ereg);
846
0
          if (!sieve_binary_read_unsigned(sblock, &offset, &version) ||
847
0
              !sieve_binary_read_unsigned(sblock, &offset, &ereg->block_id)) {
848
0
            result = -1;
849
0
          } else if (!sieve_extension_version_is(ext, version)) {
850
0
            e_debug(sbin->event, "open: "
851
0
              "binary was compiled with different version "
852
0
              "of the '%s' extension (compiled v%d, expected v%d;"
853
0
              "automatically fixed when re-compiled)",
854
0
              sieve_extension_name(ext), version,
855
0
              sieve_extension_version(ext));
856
0
            result = 0;
857
0
          }
858
0
        }
859
0
      } else {
860
0
        result = -1;
861
0
      }
862
0
    } T_END;
863
0
  }
864
865
0
  return result;
866
0
}
867
868
static bool
869
_sieve_binary_open(struct sieve_binary *sbin, enum sieve_error *error_code_r)
870
0
{
871
0
  bool result = TRUE;
872
0
  off_t offset = 0;
873
0
  struct sieve_binary_block *ext_block;
874
0
  unsigned int i;
875
0
  int ret;
876
877
  /* Read header */
878
879
0
  ret = sieve_binary_file_read_header(sbin, sbin->file->fd,
880
0
              &sbin->header, error_code_r);
881
0
  if (ret < 0)
882
0
    return FALSE;
883
0
  offset = sbin->header.hdr_size;
884
885
  /* Load block index */
886
887
0
  for (i = 0; i < sbin->header.blocks && result; i++) {
888
0
    T_BEGIN {
889
0
      if (!_read_block_index_record(sbin, &offset, i))
890
0
        result = FALSE;
891
0
    } T_END;
892
0
  }
893
894
0
  if (!result) {
895
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
896
0
    return FALSE;
897
0
  }
898
899
  /* Load extensions used by this binary */
900
901
0
  T_BEGIN {
902
0
    ext_block = sieve_binary_block_get(
903
0
      sbin, SBIN_SYSBLOCK_EXTENSIONS);
904
0
    if (ext_block == NULL) {
905
0
      result = FALSE;
906
0
    } else if ((ret = _read_extensions(ext_block)) <= 0) {
907
0
      if (ret < 0) {
908
0
        e_error(sbin->event, "open: binary is corrupt: "
909
0
          "failed to load extension block");
910
0
      }
911
0
      result = FALSE;
912
0
    }
913
0
  } T_END;
914
915
0
  if (!result) {
916
0
    *error_code_r = SIEVE_ERROR_NOT_VALID;
917
0
    return FALSE;
918
0
  }
919
0
  return TRUE;
920
0
}
921
922
int sieve_binary_open(struct sieve_instance *svinst, const char *path,
923
          struct sieve_script *script, struct sieve_binary **sbin_r,
924
          enum sieve_error *error_code_r)
925
0
{
926
0
  struct sieve_binary_extension_reg *const *regs;
927
0
  unsigned int ext_count, i;
928
0
  struct sieve_binary *sbin;
929
0
  struct sieve_binary_file *file;
930
931
0
  i_assert(script == NULL || sieve_script_svinst(script) == svinst);
932
0
  *sbin_r = NULL;
933
0
  sieve_error_args_init(&error_code_r, NULL);
934
935
  /* Create binary object */
936
0
  sbin = sieve_binary_create(svinst, script);
937
0
  sbin->path = p_strdup(sbin->pool, path);
938
939
0
  if (sieve_binary_file_open(sbin, path, &file, error_code_r) < 0) {
940
0
    sieve_binary_unref(&sbin);
941
0
    return -1;
942
0
  }
943
944
0
  sbin->file = file;
945
946
0
  event_set_append_log_prefix(
947
0
    sbin->event,
948
0
    t_strdup_printf("binary %s: ", path));
949
950
0
  if (!_sieve_binary_open(sbin, error_code_r)) {
951
0
    sieve_binary_unref(&sbin);
952
0
    return -1;
953
0
  }
954
955
0
  sieve_binary_activate(sbin);
956
957
  /* Signal open event to extensions */
958
0
  regs = array_get(&sbin->extensions, &ext_count);
959
0
  for (i = 0; i < ext_count; i++) {
960
0
    const struct sieve_binary_extension *binext = regs[i]->binext;
961
962
0
    if (binext != NULL && binext->binary_open != NULL &&
963
0
        !binext->binary_open(regs[i]->extension, sbin,
964
0
           regs[i]->context)) {
965
      /* Extension thinks its corrupt */
966
0
      *error_code_r = SIEVE_ERROR_NOT_VALID;
967
0
      sieve_binary_unref(&sbin);
968
0
      return -1;
969
0
    }
970
0
  }
971
972
0
  *sbin_r = sbin;
973
0
  return 0;
974
0
}
975
976
int sieve_binary_check_executable(struct sieve_binary *sbin,
977
          enum sieve_error *error_code_r,
978
          const char **client_error_r)
979
0
{
980
0
  *client_error_r = NULL;
981
0
  sieve_error_args_init(&error_code_r, NULL);
982
983
0
  if (HAS_ALL_BITS(sbin->header.flags,
984
0
       SIEVE_BINARY_FLAG_RESOURCE_LIMIT)) {
985
0
    e_debug(sbin->event,
986
0
      "Binary execution is blocked: "
987
0
      "Cumulative resource usage limit exceeded "
988
0
      "(resource limit flag is set)");
989
0
    *error_code_r = SIEVE_ERROR_RESOURCE_LIMIT;
990
0
    *client_error_r = "cumulative resource usage limit exceeded";
991
0
    return 0;
992
0
  }
993
0
  return 1;
994
0
}
995
996
/*
997
 * Resource usage
998
 */
999
1000
static int
1001
sieve_binary_file_do_update_resource_usage(
1002
  struct sieve_binary *sbin, int fd, enum sieve_error *error_code_r)
1003
0
{
1004
0
  struct sieve_binary_header *header = &sbin->header;
1005
0
  struct file_lock *lock;
1006
0
  const char *error;
1007
0
  int ret;
1008
1009
0
  struct file_lock_settings lock_set = {
1010
0
    .lock_method = FILE_LOCK_METHOD_FCNTL,
1011
0
  };
1012
0
  ret = file_wait_lock(fd, sbin->path, F_WRLCK, &lock_set,
1013
0
           SIEVE_BINARY_FILE_LOCK_TIMEOUT, &lock, &error);
1014
0
  if (ret <= 0) {
1015
0
    e_error(sbin->event, "%s", error);
1016
0
    *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
1017
0
    return -1;
1018
0
  }
1019
1020
0
  ret = sieve_binary_file_read_header(sbin, fd, header, error_code_r);
1021
0
  if (ret == 0) {
1022
0
    sieve_binary_file_update_header(sbin);
1023
0
    ret = sieve_binary_file_write_header(sbin, fd, header,
1024
0
                 error_code_r);
1025
0
  }
1026
1027
0
  file_lock_free(&lock);
1028
1029
0
  return ret;
1030
0
}
1031
1032
int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
1033
              enum sieve_error *error_code_r)
1034
0
{
1035
0
  int fd, ret = 0;
1036
1037
0
  sieve_error_args_init(&error_code_r, NULL);
1038
1039
0
  sieve_binary_file_close(&sbin->file);
1040
1041
0
  if (sbin->path == NULL)
1042
0
    return 0;
1043
0
  if (sbin->header.version_major != SIEVE_BINARY_VERSION_MAJOR) {
1044
0
    return sieve_binary_save(sbin, sbin->path, TRUE, 0600,
1045
0
           error_code_r);
1046
0
  }
1047
1048
0
  fd = sieve_binary_fd_open(sbin, sbin->path, O_RDWR, error_code_r);
1049
0
  if (fd < 0) {
1050
0
    i_assert(*error_code_r != SIEVE_ERROR_NONE);
1051
0
    return -1;
1052
0
  }
1053
1054
0
  ret = sieve_binary_file_do_update_resource_usage(sbin, fd,
1055
0
               error_code_r);
1056
0
  i_assert(ret == 0 || *error_code_r != SIEVE_ERROR_NONE);
1057
1058
0
  if (close(fd) < 0) {
1059
0
    e_error(sbin->event, "update: "
1060
0
      "failed to close: close() failed: %m");
1061
0
  }
1062
1063
0
  return ret;
1064
0
}