Coverage Report

Created: 2026-10-17 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/storage/file/sieve-file-script.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "mempool.h"
5
#include "str.h"
6
#include "path-util.h"
7
#include "istream.h"
8
#include "time-util.h"
9
#include "eacces-error.h"
10
11
#include "sieve-dump.h"
12
#include "sieve-binary.h"
13
#include "sieve-script-private.h"
14
15
#include "sieve-file-storage.h"
16
17
#include <stdio.h>
18
#include <unistd.h>
19
#include <ctype.h>
20
#include <time.h>
21
#include <fcntl.h>
22
23
/*
24
 * Filename to name/name to filename
25
 */
26
27
const char *sieve_script_file_get_scriptname(const char *filename)
28
0
{
29
0
  const char *ext;
30
31
  /* Extract the script name */
32
0
  ext = strrchr(filename, '.');
33
0
  if (ext == NULL || ext == filename ||
34
0
      strcmp(ext, "."SIEVE_SCRIPT_FILEEXT) != 0)
35
0
    return NULL;
36
37
0
  return t_strdup_until(filename, ext);
38
0
}
39
40
bool sieve_script_file_has_extension(const char *filename)
41
0
{
42
0
  return (sieve_script_file_get_scriptname(filename) != NULL);
43
0
}
44
45
const char *sieve_script_file_from_name(const char *name)
46
0
{
47
0
  return t_strconcat(name, "."SIEVE_SCRIPT_FILEEXT, NULL);
48
0
}
49
50
/*
51
 * Common error handling
52
 */
53
54
static void
55
sieve_file_script_handle_error(struct sieve_file_script *fscript,
56
             const char *op, const char *path,
57
             const char *name)
58
0
{
59
0
  struct sieve_script *script = &fscript->script;
60
0
  const char *abspath, *error;
61
62
0
  switch (errno) {
63
0
  case ENOENT:
64
0
    if (t_abspath(path, &abspath, &error) < 0) {
65
0
      sieve_script_set_critical(script,
66
0
              "t_abspath(%s) failed: %s",
67
0
              path, error);
68
0
      break;
69
0
    }
70
0
    e_debug(script->event, "File '%s' not found", abspath);
71
0
    sieve_script_set_not_found_error(script, name);
72
0
    break;
73
0
  case EACCES:
74
0
    sieve_script_set_critical(script,
75
0
            "Failed to %s sieve script: %s",
76
0
            op, eacces_error_get(op, path));
77
0
    script->storage->error_code = SIEVE_ERROR_NO_PERMISSION;
78
0
    break;
79
0
  default:
80
0
    sieve_script_set_critical(
81
0
      script, "Failed to %s sieve script: %s(%s) failed: %m",
82
0
      op, op, path);
83
0
    break;
84
0
  }
85
0
}
86
87
/*
88
 *
89
 */
90
91
static struct sieve_file_script *sieve_file_script_alloc(void)
92
0
{
93
0
  struct sieve_file_script *fscript;
94
0
  pool_t pool;
95
96
0
  pool = pool_alloconly_create("sieve_file_script", 2048);
97
0
  fscript = p_new(pool, struct sieve_file_script, 1);
98
0
  fscript->script = sieve_file_script;
99
0
  fscript->script.pool = pool;
100
101
0
  return fscript;
102
0
}
103
104
int sieve_file_script_init_from_filename(struct sieve_file_storage *fstorage,
105
           const char *filename,
106
           const char *scriptname,
107
           struct sieve_file_script **fscript_r)
108
0
{
109
0
  struct sieve_storage *storage = &fstorage->storage;
110
0
  struct sieve_file_script *fscript = NULL;
111
112
0
  *fscript_r = NULL;
113
114
  /* Prevent initializing the active script link as a script when it
115
     resides in the sieve storage directory.
116
   */
117
0
  if (scriptname != NULL && fstorage->link_path != NULL &&
118
0
      *(fstorage->link_path) == '\0') {
119
0
    if (strcmp(filename, fstorage->active_fname) == 0) {
120
0
      sieve_storage_set_error(
121
0
        storage, SIEVE_ERROR_NOT_FOUND,
122
0
        "Script '%s' does not exist.", scriptname);
123
0
      return -1;
124
0
    }
125
0
  }
126
127
0
  fscript = sieve_file_script_alloc();
128
0
  sieve_script_init(&fscript->script, storage, &sieve_file_script,
129
0
        scriptname);
130
0
  fscript->filename = p_strdup(fscript->script.pool, filename);
131
132
0
  event_add_str(fscript->script.event, "sieve_script_file_path",
133
0
          sieve_file_storage_path_extend(fstorage, filename));
134
135
0
  *fscript_r = fscript;
136
0
  return 0;
137
0
}
138
139
int sieve_file_script_open_from_filename(struct sieve_file_storage *fstorage,
140
           const char *filename,
141
           const char *scriptname,
142
           struct sieve_file_script **fscript_r)
143
0
{
144
0
  struct sieve_file_script *fscript;
145
146
0
  *fscript_r = NULL;
147
148
0
  if (sieve_file_script_init_from_filename(fstorage, filename, scriptname,
149
0
             &fscript) < 0)
150
0
    return -1;
151
152
0
  if (sieve_script_open(&fscript->script, NULL) < 0) {
153
0
    struct sieve_script *script = &fscript->script;
154
155
0
    sieve_script_unref(&script);
156
0
    return -1;
157
0
  }
158
159
0
  *fscript_r = fscript;
160
0
  return 0;
161
0
}
162
163
int sieve_file_script_init_from_name(struct sieve_file_storage *fstorage,
164
             const char *name,
165
             struct sieve_file_script **fscript_r)
166
0
{
167
0
  struct sieve_storage *storage = &fstorage->storage;
168
0
  struct sieve_file_script *fscript;
169
170
0
  *fscript_r = NULL;
171
172
0
  if (name != NULL && S_ISDIR(fstorage->st.st_mode)) {
173
0
    return sieve_file_script_init_from_filename(
174
0
      fstorage, sieve_script_file_from_name(name), name,
175
0
      fscript_r);
176
0
  }
177
178
0
  fscript = sieve_file_script_alloc();
179
0
  sieve_script_init(&fscript->script, storage, &sieve_file_script, name);
180
181
0
  event_add_str(fscript->script.event, "sieve_script_file_path",
182
0
          fstorage->active_path);
183
184
0
  *fscript_r = fscript;
185
0
  return 0;
186
0
}
187
188
int sieve_file_script_open_from_name(struct sieve_file_storage *fstorage,
189
             const char *name,
190
             struct sieve_file_script **fscript_r)
191
0
{
192
0
  struct sieve_file_script *fscript;
193
194
0
  *fscript_r = NULL;
195
196
0
  if (sieve_file_script_init_from_name(fstorage, name, &fscript) < 0)
197
0
    return -1;
198
199
0
  if (sieve_script_open(&fscript->script, NULL) < 0) {
200
0
    struct sieve_script *script = &fscript->script;
201
202
0
    sieve_script_unref(&script);
203
0
    return -1;
204
0
  }
205
206
0
  *fscript_r = fscript;
207
0
  return 0;
208
0
}
209
210
int sieve_file_script_init_from_path(struct sieve_file_storage *fstorage,
211
             const char *path, const char *scriptname,
212
             struct sieve_file_script **fscript_r)
213
0
{
214
0
  struct sieve_storage *storage = &fstorage->storage;
215
0
  struct sieve_instance *svinst = storage->svinst;
216
0
  struct sieve_file_storage *fsubstorage;
217
0
  struct sieve_file_script *fscript;
218
0
  struct sieve_storage *substorage;
219
0
  enum sieve_error error_code;
220
0
  const char *error;
221
222
0
  *fscript_r = NULL;
223
224
0
  if (sieve_file_storage_init_from_path(svinst, storage->cause,
225
0
                storage->type, storage->name,
226
0
                path, 0, &fsubstorage,
227
0
                &error_code, &error) < 0) {
228
0
    sieve_storage_set_error(storage, error_code, "%s", error);
229
0
    return -1;
230
0
  }
231
0
  substorage = &fsubstorage->storage;
232
233
0
  fscript = sieve_file_script_alloc();
234
0
  sieve_script_init(&fscript->script, substorage, &sieve_file_script,
235
0
        scriptname);
236
0
  sieve_storage_unref(&substorage);
237
238
0
  event_add_str(fscript->script.event, "sieve_script_file_path",
239
0
          fstorage->active_path);
240
241
0
  *fscript_r = fscript;
242
0
  return 0;
243
0
}
244
245
int sieve_file_script_open_from_path(struct sieve_file_storage *fstorage,
246
             const char *path, const char *scriptname,
247
             struct sieve_file_script **fscript_r)
248
0
{
249
0
  struct sieve_storage *storage = &fstorage->storage;
250
0
  struct sieve_file_script *fscript;
251
252
0
  *fscript_r = NULL;
253
254
0
  if (sieve_file_script_init_from_path(fstorage, path, scriptname,
255
0
               &fscript) < 0)
256
0
    return -1;
257
258
0
  if (sieve_script_open(&fscript->script, NULL) < 0) {
259
0
    struct sieve_script *script = &fscript->script;
260
261
0
    sieve_storage_copy_error(storage, script->storage);
262
0
    sieve_script_unref(&script);
263
0
    return -1;
264
0
  }
265
266
0
  *fscript_r = fscript;
267
0
  return 0;
268
0
}
269
270
/*
271
 * Open
272
 */
273
274
static int
275
sieve_file_script_stat(const char *path, struct stat *st, struct stat *lnk_st)
276
0
{
277
0
  if (lstat(path, st) < 0)
278
0
    return -1;
279
280
0
  *lnk_st = *st;
281
282
0
  if (S_ISLNK(st->st_mode) && stat(path, st) < 0)
283
0
    return -1;
284
0
  return 0;
285
0
}
286
287
/* TOCTOU-safe variant of sieve_file_script_stat() for directory storages.
288
   Resolves the script entry beneath fstorage->dir_fd, refusing symlinks
289
   whose target escapes the storage directory. *st gets the (resolved)
290
   target's stat; *lnk_st gets the directory entry's own stat (i.e.
291
   AT_SYMLINK_NOFOLLOW), matching the semantics of the unsafe variant. */
292
static int
293
sieve_file_script_stat_safe(struct sieve_file_storage *fstorage,
294
          const char *filename, struct stat *st,
295
          struct stat *lnk_st, const char **error_r)
296
0
{
297
0
  int fd, saved_errno;
298
299
0
  i_assert(fstorage->dir_fd >= 0);
300
301
0
  if (fstatat(fstorage->dir_fd, filename, lnk_st,
302
0
        AT_SYMLINK_NOFOLLOW) < 0) {
303
0
    *error_r = t_strdup_printf(
304
0
      "fstatat(%s/%s) failed: %m",
305
0
      fstorage->path, filename);
306
0
    return -1;
307
0
  }
308
309
0
  if (!S_ISLNK(lnk_st->st_mode)) {
310
0
    *st = *lnk_st;
311
0
    return 0;
312
0
  }
313
314
  /* Symlink: open it via the safe walker (which refuses any target
315
     that leaves fstorage->dir_fd) and read st from the resulting fd.
316
     The fd is only used to fetch the resolved stat and is closed
317
     immediately. Open with O_NONBLOCK so that a target which resolves
318
     to a FIFO or other special file inside the storage directory only
319
     yields its stat instead of blocking the delivery process. */
320
0
  if (sieve_file_storage_open_safe(fstorage, filename,
321
0
           O_RDONLY | O_NONBLOCK,
322
0
           &fd, error_r) < 0)
323
0
    return -1;
324
325
0
  if (fstat(fd, st) < 0) {
326
0
    saved_errno = errno;
327
0
    *error_r = t_strdup_printf(
328
0
      "fstat() failed for '%s/%s': %m",
329
0
      fstorage->path, filename);
330
0
    i_close_fd(&fd);
331
0
    errno = saved_errno;
332
0
    return -1;
333
0
  }
334
0
  i_close_fd(&fd);
335
0
  return 0;
336
0
}
337
338
static const char *
339
path_split_filename(const char *path, const char **dir_path_r)
340
0
{
341
0
  const char *filename;
342
343
0
  filename = strrchr(path, '/');
344
0
  if (filename == NULL) {
345
0
    *dir_path_r = "";
346
0
    filename = path;
347
0
  } else {
348
0
    *dir_path_r = t_strdup_until(path, filename);
349
0
    filename++;
350
0
  }
351
0
  return filename;
352
0
}
353
354
static int sieve_file_script_open(struct sieve_script *script)
355
0
{
356
0
  struct sieve_file_script *fscript =
357
0
    container_of(script, struct sieve_file_script, script);
358
0
  struct sieve_storage *storage = script->storage;
359
0
  struct sieve_file_storage *fstorage =
360
0
    container_of(storage, struct sieve_file_storage, storage);
361
0
  pool_t pool = script->pool;
362
0
  const char *filename, *name, *path;
363
0
  const char *dir_path, *basename, *bin_path, *bin_prefix;
364
0
  struct stat st, lnk_st;
365
0
  bool success = TRUE;
366
0
  int ret = 0;
367
368
0
  filename = fscript->filename;
369
0
  basename = NULL;
370
0
  name = script->name;
371
0
  st = fstorage->st;
372
0
  lnk_st = fstorage->lnk_st;
373
374
0
  if (name == NULL && storage->script_name != NULL &&
375
0
      *storage->script_name != '\0')
376
0
    name = storage->script_name;
377
378
0
  T_BEGIN {
379
0
    if (S_ISDIR(st.st_mode)) {
380
      /* Storage is a directory */
381
0
      path = fstorage->path;
382
383
0
      if ((filename == NULL || *filename == '\0') &&
384
0
          name != NULL && *name != '\0') {
385
        /* Name is used to find actual filename */
386
0
        filename = sieve_script_file_from_name(name);
387
0
        basename = name;
388
0
      }
389
0
      if (filename == NULL || *filename == '\0') {
390
0
        sieve_script_set_critical(
391
0
          script, "Sieve script file path '%s' is a directory.", path);
392
0
        success = FALSE;
393
0
      } else {
394
        /* Extend storage path with filename */
395
0
        if (name == NULL) {
396
0
          if (basename == NULL &&
397
0
              (basename = sieve_script_file_get_scriptname(filename)) == NULL)
398
0
            basename = filename;
399
0
          name = basename;
400
0
        } else if (basename == NULL) {
401
0
          basename = name;
402
0
        }
403
0
        dir_path = path;
404
405
0
        path = sieve_file_storage_path_extend(fstorage, filename);
406
0
        if (fstorage->dir_fd >= 0) {
407
0
          const char *serror;
408
409
0
          ret = sieve_file_script_stat_safe(
410
0
            fstorage, filename, &st,
411
0
            &lnk_st, &serror);
412
0
          if (ret < 0 && errno == ELOOP) {
413
0
            sieve_script_set_critical(
414
0
              script,
415
0
              "Failed to open sieve script: %s",
416
0
              serror);
417
0
            script->storage->error_code =
418
0
              SIEVE_ERROR_NO_PERMISSION;
419
0
            success = FALSE;
420
0
          }
421
0
        } else {
422
0
          ret = sieve_file_script_stat(
423
0
            path, &st, &lnk_st);
424
0
        }
425
0
      }
426
427
0
    } else {
428
      /* Storage is a single file */
429
0
      path = fstorage->active_path;
430
431
      /* Extract filename from path */
432
0
      filename = path_split_filename(path, &dir_path);
433
434
0
      basename = sieve_script_file_get_scriptname(filename);
435
0
      if (basename == NULL)
436
0
        basename = filename;
437
438
0
      if (name == NULL)
439
0
        name = basename;
440
0
    }
441
442
0
    if (success) {
443
0
      if (ret < 0) {
444
        /* Make sure we have a script name for the error
445
         */
446
0
        if (name == NULL) {
447
0
          i_assert(basename != NULL);
448
0
          name = basename;
449
0
        }
450
0
        sieve_file_script_handle_error(fscript, "stat",
451
0
                     path, name);
452
0
        success = FALSE;
453
454
0
      } else if (!S_ISREG(st.st_mode)) {
455
0
        sieve_script_set_critical(
456
0
          script, "Sieve script file '%s' is not a regular file.",
457
0
          path);
458
0
        success = FALSE;
459
0
      }
460
0
    }
461
462
0
    if (success) {
463
0
      const char *bpath, *bfile, *bprefix;
464
465
0
      if (storage->bin_path != NULL) {
466
0
        bpath = storage->bin_path;
467
0
        bfile = sieve_binfile_from_name(name);
468
0
        bprefix = name;
469
470
0
      } else {
471
0
        bpath = dir_path;
472
0
        bfile = sieve_binfile_from_name(basename);
473
0
        bprefix = basename;
474
0
      }
475
476
0
      if (*bpath == '\0') {
477
0
        bin_path = bfile;
478
0
        bin_prefix = bprefix;
479
0
      } else if (bpath[strlen(bpath)-1] == '/') {
480
0
        bin_path = t_strconcat(bpath, bfile, NULL);
481
0
        bin_prefix = t_strconcat(bpath, bprefix, NULL);
482
0
      } else {
483
0
        bin_path = t_strconcat(bpath, "/", bfile, NULL);
484
0
        bin_prefix = t_strconcat(bpath, "/", bprefix, NULL);
485
0
      }
486
487
0
      fscript->st = st;
488
0
      fscript->lnk_st = lnk_st;
489
0
      fscript->path = p_strdup(pool, path);
490
0
      fscript->filename = p_strdup(pool, filename);
491
0
      fscript->dir_path = p_strdup(pool, dir_path);
492
0
      fscript->bin_path = p_strdup(pool, bin_path);
493
0
      fscript->bin_prefix = p_strdup(pool, bin_prefix);
494
495
0
      if (fscript->script.name == NULL)
496
0
        fscript->script.name = p_strdup(pool, basename);
497
498
0
      event_add_str(script->event, "sieve_script_file_path",
499
0
              fscript->path);
500
0
    }
501
0
  } T_END;
502
503
0
  return (success ? 0 : -1);
504
0
}
505
506
static int
507
sieve_file_script_get_stream(struct sieve_script *script,
508
           struct istream **stream_r)
509
0
{
510
0
  struct sieve_file_script *fscript =
511
0
    container_of(script, struct sieve_file_script, script);
512
0
  struct sieve_file_storage *fstorage =
513
0
    container_of(script->storage, struct sieve_file_storage,
514
0
           storage);
515
0
  struct stat st;
516
0
  struct istream *result;
517
0
  const char *error;
518
0
  int fd;
519
520
  /* For directory-based storage, open the script via the storage
521
     directory fd so that path resolution refuses to follow symlinks
522
     whose (recursive) target leaves the storage directory. The fd-based
523
     walk also makes this TOCTOU-safe: even if intermediate path
524
     components are mutated between the stat in sieve_file_script_open()
525
     and this call, the resolution stays within the original directory.
526
     Single-file storages have no dir_fd, so fall back to a plain open(). */
527
0
  if (fstorage->dir_fd >= 0 && fscript->filename != NULL &&
528
0
      *fscript->filename != '\0') {
529
0
    if (sieve_file_storage_open_safe(fstorage, fscript->filename,
530
0
             O_RDONLY, &fd, &error) < 0) {
531
0
      if (errno == ELOOP) {
532
0
        sieve_script_set_critical(
533
0
          script,
534
0
          "Failed to open sieve script: %s",
535
0
          error);
536
0
        script->storage->error_code =
537
0
          SIEVE_ERROR_NO_PERMISSION;
538
0
        return -1;
539
0
      }
540
0
      sieve_file_script_handle_error(fscript, "open",
541
0
                   fscript->path,
542
0
                   fscript->script.name);
543
0
      return -1;
544
0
    }
545
0
  } else {
546
0
    fd = open(fscript->path, O_RDONLY);
547
0
    if (fd < 0) {
548
0
      sieve_file_script_handle_error(fscript, "open",
549
0
                   fscript->path,
550
0
                   fscript->script.name);
551
0
      return -1;
552
0
    }
553
0
  }
554
555
0
  if (fstat(fd, &st) != 0) {
556
0
    sieve_script_set_critical(
557
0
      script,
558
0
      "Failed to open sieve script: fstat(fd=%s) failed: %m",
559
0
      fscript->path);
560
0
    result = NULL;
561
  /* Re-check the file type just to be sure */
562
0
  } else if (!S_ISREG(st.st_mode)) {
563
0
    sieve_script_set_critical(
564
0
      script, "Sieve script file '%s' is not a regular file",
565
0
      fscript->path);
566
0
    result = NULL;
567
0
  } else {
568
0
    result = i_stream_create_fd_autoclose(
569
0
      &fd, SIEVE_FILE_READ_BLOCK_SIZE);
570
0
    fscript->st = fscript->lnk_st = st;
571
0
  }
572
573
0
  if (result == NULL) {
574
    /* Something went wrong, close the fd */
575
0
    if (fd >= 0 && close(fd) != 0) {
576
0
      e_error(script->event,
577
0
        "Failed to close sieve script: "
578
0
        "close(fd=%s) failed: %m", fscript->path);
579
0
    }
580
0
    return -1;
581
0
  }
582
583
0
  *stream_r = result;
584
0
  return 0;
585
0
}
586
587
/*
588
 * Binary
589
 */
590
591
static int
592
sieve_file_script_binary_read_metadata(struct sieve_script *script,
593
               struct sieve_binary_block *sblock,
594
               sieve_size_t *offset)
595
0
{
596
0
  struct sieve_instance *svinst = script->storage->svinst;
597
0
  struct sieve_file_script *fscript =
598
0
    container_of(script, struct sieve_file_script, script);
599
0
  struct sieve_binary *sbin = sieve_binary_block_get_binary(sblock);
600
0
  string_t *path;
601
602
  /* Open if not open already */
603
0
  if (sieve_script_open(script, NULL) < 0)
604
0
    return 0;
605
606
  /* Metadata: path */
607
0
  if (!sieve_binary_read_string(sblock, offset, &path)) {
608
0
    e_error(script->event,
609
0
      "Binary '%s' has invalid metadata for script '%s': "
610
0
      "Invalid file path",
611
0
      sieve_binary_path(sbin), sieve_script_label(script));
612
0
    return -1;
613
0
  }
614
0
  i_assert(fscript->path != NULL);
615
0
  if (strcmp(str_c(path), fscript->path) != 0) {
616
0
    e_debug(script->event,
617
0
      "Binary '%s' reports different file path for script '%s' "
618
0
      "('%s' rather than '%s')",
619
0
      sieve_binary_path(sbin), sieve_script_label(script),
620
0
      str_c(path), fscript->path);
621
0
    return 0;
622
0
  }
623
624
0
  const struct stat *sstat, *bstat;
625
626
0
  bstat = sieve_binary_stat(sbin);
627
0
  if (fscript->st.st_mtime > fscript->lnk_st.st_mtime ||
628
0
      (fscript->st.st_mtime == fscript->lnk_st.st_mtime &&
629
0
       ST_MTIME_NSEC(fscript->st) >= ST_MTIME_NSEC(fscript->lnk_st))) {
630
0
    sstat = &fscript->st;
631
0
  } else {
632
0
    sstat = &fscript->lnk_st;
633
0
  }
634
635
0
  if (bstat->st_mtime < sstat->st_mtime ||
636
0
      (bstat->st_mtime == sstat->st_mtime &&
637
0
       ST_MTIME_NSEC(*bstat) <= ST_MTIME_NSEC(*sstat))) {
638
0
    if (svinst->debug) {
639
0
      e_debug(script->event,
640
0
        "Sieve binary '%s' is not newer "
641
0
        "than the Sieve script '%s' (path=%s, %s.%lu <= %s.%lu)",
642
0
        sieve_binary_path(sbin), sieve_script_label(script),
643
0
        fscript->path,
644
0
        t_strflocaltime("%Y-%m-%d %H:%M:%S",
645
0
            bstat->st_mtime),
646
0
        ST_MTIME_NSEC(*bstat),
647
0
        t_strflocaltime("%Y-%m-%d %H:%M:%S",
648
0
            sstat->st_mtime),
649
0
        ST_MTIME_NSEC(*sstat));
650
0
    }
651
0
    return 0;
652
0
  }
653
654
0
  return 1;
655
0
}
656
657
static void
658
sieve_file_script_binary_write_metadata(struct sieve_script *script,
659
          struct sieve_binary_block *sblock)
660
0
{
661
0
  struct sieve_file_script *fscript =
662
0
    container_of(script, struct sieve_file_script, script);
663
664
0
  sieve_binary_emit_cstring(sblock, fscript->path);
665
0
}
666
667
static bool
668
sieve_file_script_binary_dump_metadata(struct sieve_script *script ATTR_UNUSED,
669
               struct sieve_dumptime_env *denv,
670
               struct sieve_binary_block *sblock,
671
               sieve_size_t *offset)
672
0
{
673
0
  string_t *path;
674
675
0
  if (!sieve_binary_read_string(sblock, offset, &path))
676
0
    return FALSE;
677
0
  sieve_binary_dumpf(denv, "file.path = %s\n", str_c(path));
678
679
0
  return TRUE;
680
0
}
681
682
static int
683
sieve_file_script_binary_load(struct sieve_script *script,
684
            struct sieve_binary **sbin_r)
685
0
{
686
0
  struct sieve_file_script *fscript =
687
0
    container_of(script, struct sieve_file_script, script);
688
689
0
  return sieve_script_binary_load_default(script, fscript->bin_path,
690
0
            sbin_r);
691
0
}
692
693
static int
694
sieve_file_script_binary_save(struct sieve_script *script,
695
            struct sieve_binary *sbin, bool update)
696
0
{
697
0
  struct sieve_file_script *fscript =
698
0
    container_of(script, struct sieve_file_script, script);
699
700
0
  return sieve_script_binary_save_default(
701
0
    script, sbin, fscript->bin_path, update,
702
0
    (fscript->st.st_mode & 0777));
703
0
}
704
705
static const char *
706
sieve_file_script_binary_get_prefix(struct sieve_script *script)
707
0
{
708
0
  struct sieve_file_script *fscript =
709
0
    container_of(script, struct sieve_file_script, script);
710
711
0
  return fscript->bin_prefix;
712
0
}
713
714
/*
715
 * Management
716
 */
717
718
static int sieve_file_storage_script_is_active(struct sieve_script *script)
719
0
{
720
0
  struct sieve_file_script *fscript =
721
0
    container_of(script, struct sieve_file_script, script);
722
0
  struct sieve_file_storage *fstorage =
723
0
    container_of(script->storage, struct sieve_file_storage,
724
0
           storage);
725
0
  const char *afile;
726
0
  int ret = 0;
727
728
0
  T_BEGIN {
729
0
    ret = sieve_file_storage_active_script_get_file(
730
0
      fstorage, &afile);
731
732
0
    if (ret > 0) {
733
      /* Is the requested script active? */
734
0
      ret = (strcmp(fscript->filename, afile) == 0 ? 1 : 0);
735
0
    }
736
0
  } T_END;
737
738
0
  return ret;
739
0
}
740
741
static int sieve_file_storage_script_delete(struct sieve_script *script)
742
0
{
743
0
  struct sieve_file_script *fscript =
744
0
    container_of(script, struct sieve_file_script, script);
745
0
  int ret = 0;
746
747
0
  if (sieve_file_storage_pre_modify(script->storage) < 0)
748
0
    return -1;
749
750
0
  ret = unlink(fscript->path);
751
0
  if (ret < 0) {
752
0
    if (errno == ENOENT) {
753
0
      sieve_script_set_error(script, SIEVE_ERROR_NOT_FOUND,
754
0
                 "Sieve script does not exist.");
755
0
    } else {
756
0
      sieve_script_set_critical(
757
0
        script,
758
0
        "Performing unlink() failed on sieve file '%s': %m",
759
0
        fscript->path);
760
0
    }
761
0
  }
762
763
  /* Best-effort: drop the compiled binary as well so it does not
764
     linger as a stale orphan. */
765
0
  if (fscript->bin_path != NULL &&
766
0
      unlink(fscript->bin_path) < 0 && errno != ENOENT) {
767
0
    e_error(script->event,
768
0
      "unlink(%s) failed: %m", fscript->bin_path);
769
0
  }
770
0
  return ret;
771
0
}
772
773
static int
774
_sieve_file_storage_script_activate(struct sieve_file_script *fscript)
775
0
{
776
0
  struct sieve_script *script = &fscript->script;
777
0
  struct sieve_storage *storage = script->storage;
778
0
  struct sieve_file_storage *fstorage =
779
0
    container_of(storage, struct sieve_file_storage, storage);
780
0
  struct stat st;
781
0
  const char *link_path, *afile;
782
0
  int activated = 0;
783
0
  int ret;
784
785
  /* Find out whether there is an active script, but recreate
786
     the symlink either way. This way, any possible error in the symlink
787
     resolves automatically. This step is only necessary to provide a
788
     proper return value indicating whether the script was already active.
789
   */
790
0
  ret = sieve_file_storage_active_script_get_file(fstorage, &afile);
791
792
  /* Is the requested script already active? */
793
0
  if (ret <= 0 || strcmp(fscript->filename, afile) != 0)
794
0
    activated = 1;
795
796
0
  i_assert(fstorage->link_path != NULL);
797
798
  /* Check the scriptfile we are trying to activate */
799
0
  if (lstat(fscript->path, &st) != 0) {
800
0
    sieve_script_set_critical(
801
0
      script,
802
0
      "Failed to activate Sieve script: lstat(%s) failed: %m.",
803
0
      fscript->path);
804
0
    return -1;
805
0
  }
806
807
  /* Rescue a possible ".dovecot.sieve" regular file remaining from old
808
     installations.
809
   */
810
0
  if (!sieve_file_storage_active_rescue_regular(fstorage)) {
811
    /* Rescue failed, manual intervention is necessary */
812
0
    return -1;
813
0
  }
814
815
  /* Just try to create the symlink first */
816
0
  link_path = t_strconcat(fstorage->link_path, fscript->filename, NULL);
817
818
0
  ret = symlink(link_path, fstorage->active_path);
819
0
  if (ret < 0) {
820
0
    if (errno == EEXIST) {
821
0
      ret = sieve_file_storage_active_replace_link(
822
0
        fstorage, link_path);
823
0
      if (ret < 0)
824
0
        return ret;
825
0
    } else {
826
      /* Other error, critical */
827
0
      sieve_script_set_critical(
828
0
        script, "Failed to activate Sieve script: "
829
0
        "symlink(%s, %s) failed: %m",
830
0
        link_path, fstorage->active_path);
831
0
      return -1;
832
0
    }
833
0
  }
834
0
  return activated;
835
0
}
836
837
static int sieve_file_storage_script_activate(struct sieve_script *script)
838
0
{
839
0
  struct sieve_file_script *fscript =
840
0
    container_of(script, struct sieve_file_script, script);
841
0
  int ret;
842
843
0
  if (sieve_file_storage_pre_modify(script->storage) < 0)
844
0
    return -1;
845
846
0
  T_BEGIN {
847
0
    ret = _sieve_file_storage_script_activate(fscript);
848
0
  } T_END;
849
850
0
  return ret;
851
0
}
852
853
static int
854
sieve_file_storage_script_rename(struct sieve_script *script,
855
         const char *newname)
856
0
{
857
0
  struct sieve_file_script *fscript =
858
0
    container_of(script, struct sieve_file_script, script);
859
0
  struct sieve_storage *storage = script->storage;
860
0
  struct sieve_file_storage *fstorage =
861
0
    container_of(storage, struct sieve_file_storage, storage);
862
0
  const char *newpath, *newfile, *link_path;
863
0
  int ret = 0;
864
865
0
  if (sieve_file_storage_pre_modify(storage) < 0)
866
0
    return -1;
867
868
0
  T_BEGIN {
869
0
    newfile = sieve_script_file_from_name(newname);
870
0
    newpath = t_strconcat(fstorage->path, "/", newfile, NULL);
871
872
    /* The normal rename() system call overwrites the existing file
873
       without notice. Also, active scripts must not be disrupted by
874
       renaming a script. That is why we use a link(newpath)
875
       [activate newpath] unlink(oldpath)
876
     */
877
878
    /* Link to the new path */
879
0
    ret = link(fscript->path, newpath);
880
0
    if (ret >= 0) {
881
      /* Is the requested script active? */
882
0
      if (sieve_script_is_active(script) > 0) {
883
        /* Active; make active link point to the new
884
           copy */
885
0
        i_assert(fstorage->link_path != NULL);
886
0
        link_path = t_strconcat(fstorage->link_path,
887
0
              newfile, NULL);
888
889
0
        ret = sieve_file_storage_active_replace_link(
890
0
          fstorage, link_path);
891
0
      }
892
893
0
      if (ret >= 0) {
894
        /* If all is good, remove the old link */
895
0
        if (unlink(fscript->path) < 0) {
896
0
          e_error(script->event,
897
0
            "Failed to clean up after rename: "
898
0
            "unlink(%s) failed: %m",
899
0
            fscript->path);
900
0
        }
901
902
        /* Drop the old compiled binary; the renamed
903
           script will recompile under its new name. */
904
0
        if (fscript->bin_path != NULL &&
905
0
            unlink(fscript->bin_path) < 0 &&
906
0
            errno != ENOENT) {
907
0
          e_error(script->event,
908
0
            "unlink(%s) failed: %m",
909
0
            fscript->bin_path);
910
0
        }
911
912
0
        if (script->name != NULL && *script->name != '\0')
913
0
          script->name = p_strdup(script->pool, newname);
914
0
        fscript->path = p_strdup(script->pool, newpath);
915
0
        fscript->filename = p_strdup(script->pool, newfile);
916
0
      } else {
917
        /* If something went wrong, remove the new link
918
           to restore previous state */
919
0
        if (unlink(newpath) < 0) {
920
0
          e_error(script->event,
921
0
            "Failed to clean up after failed rename: "
922
0
            "unlink(%s) failed: %m", newpath);
923
0
        }
924
0
      }
925
0
    } else {
926
      /* Our efforts failed right away */
927
0
      switch (errno) {
928
0
      case ENOENT:
929
0
        sieve_script_set_error(
930
0
          script, SIEVE_ERROR_NOT_FOUND,
931
0
          "Sieve script does not exist.");
932
0
        break;
933
0
      case EEXIST:
934
0
        sieve_script_set_error(
935
0
          script, SIEVE_ERROR_EXISTS,
936
0
          "A sieve script with that name already exists.");
937
0
        break;
938
0
      default:
939
0
        sieve_script_set_critical(
940
0
          script, "Failed to rename Sieve script: "
941
0
          "link(%s, %s) failed: %m",
942
0
          fscript->path, newpath);
943
0
      }
944
0
    }
945
0
  } T_END;
946
947
0
  return ret;
948
0
}
949
950
/*
951
 * Properties
952
 */
953
954
static int
955
sieve_file_script_get_size(const struct sieve_script *script, uoff_t *size_r)
956
0
{
957
0
  const struct sieve_file_script *fscript =
958
0
    container_of(script, const struct sieve_file_script, script);
959
960
0
  *size_r = fscript->st.st_size;
961
0
  return 1;
962
0
}
963
964
const char *sieve_file_script_get_dir_path(const struct sieve_script *script)
965
0
{
966
0
  const struct sieve_file_script *fscript =
967
0
    container_of(script, const struct sieve_file_script, script);
968
969
0
  if (script->driver_name != sieve_file_script.driver_name)
970
0
    return NULL;
971
972
0
  return fscript->dir_path;
973
0
}
974
975
const char *sieve_file_script_get_path(const struct sieve_script *script)
976
0
{
977
0
  const struct sieve_file_script *fscript =
978
0
    container_of(script, const struct sieve_file_script, script);
979
980
0
  if (script->driver_name != sieve_file_script.driver_name)
981
0
    return NULL;
982
983
0
  return fscript->path;
984
0
}
985
986
/*
987
 * Matching
988
 */
989
990
static int
991
sieve_file_script_cmp(const struct sieve_script *script1,
992
          const struct sieve_script *script2)
993
0
{
994
0
  const struct sieve_file_script *fscript1 =
995
0
    container_of(script1, const struct sieve_file_script, script);
996
0
  const struct sieve_file_script *fscript2 =
997
0
    container_of(script2, const struct sieve_file_script, script);
998
0
  int ret;
999
1000
0
  if (!script1->open || !script2->open) {
1001
0
    ret = sieve_storage_cmp(script1->storage, script2->storage);
1002
0
    if (ret != 0)
1003
0
      return ret;
1004
1005
0
    return null_strcmp(script1->name, script2->name);
1006
0
  }
1007
1008
0
  if (major(fscript1->st.st_dev) != major(fscript2->st.st_dev)) {
1009
0
    return (major(fscript1->st.st_dev) >
1010
0
      major(fscript2->st.st_dev) ? 1 : -1);
1011
0
  }
1012
0
  if (minor(fscript1->st.st_dev) != minor(fscript2->st.st_dev)) {
1013
0
    return (minor(fscript1->st.st_dev) >
1014
0
      minor(fscript2->st.st_dev) ? 1 : -1);
1015
0
  }
1016
1017
0
  if (fscript1->st.st_ino != fscript2->st.st_ino)
1018
0
    return (fscript1->st.st_ino > fscript2->st.st_ino ? 1 : -1);
1019
1020
0
  return 0;
1021
0
}
1022
1023
/*
1024
 * Driver definition
1025
 */
1026
1027
const struct sieve_script sieve_file_script = {
1028
  .driver_name = SIEVE_FILE_STORAGE_DRIVER_NAME,
1029
  .v = {
1030
    .open = sieve_file_script_open,
1031
1032
    .get_stream = sieve_file_script_get_stream,
1033
1034
    .binary_read_metadata = sieve_file_script_binary_read_metadata,
1035
    .binary_write_metadata =
1036
      sieve_file_script_binary_write_metadata,
1037
    .binary_dump_metadata = sieve_file_script_binary_dump_metadata,
1038
    .binary_load = sieve_file_script_binary_load,
1039
    .binary_save = sieve_file_script_binary_save,
1040
    .binary_get_prefix = sieve_file_script_binary_get_prefix,
1041
1042
    .rename = sieve_file_storage_script_rename,
1043
    .delete = sieve_file_storage_script_delete,
1044
    .is_active = sieve_file_storage_script_is_active,
1045
    .activate = sieve_file_storage_script_activate,
1046
1047
    .get_size = sieve_file_script_get_size,
1048
1049
    .cmp = sieve_file_script_cmp,
1050
  }
1051
};