1 /* sievedir.c -- functions for managing scripts in a sievedir
2 *
3 * Copyright (c) 1994-2020 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 */
42
43 #include <config.h>
44
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48
49 #include <dirent.h>
50 #include <ctype.h>
51 #include <string.h>
52 #include <syslog.h>
53 #include <assert.h>
54 #include <errno.h>
55 #include <sys/stat.h>
56 #include <sys/types.h>
57
58 #include "assert.h"
59 #include "map.h"
60 #include "sievedir.h"
61 #include "util.h"
62 #include "xstrlcpy.h"
63
64 #ifdef USE_SIEVE
65 #include "sieve/bc_parse.h"
66 #include "sieve/sieve_interface.h"
67 #endif
68
sievedir_foreach(const char * sievedir,unsigned flags,int (* func)(const char * sievedir,const char * name,struct stat * sbuf,const char * target,void * rock),void * rock)69 EXPORTED int sievedir_foreach(const char *sievedir, unsigned flags,
70 int (*func)(const char *sievedir,
71 const char *name, struct stat *sbuf,
72 const char *target, void *rock),
73 void *rock)
74 {
75 DIR *dp;
76 struct dirent *dir;
77 char path[PATH_MAX];
78 int dir_len;
79 int r = SIEVEDIR_OK;
80
81 if ((dp = opendir(sievedir)) == NULL) {
82 if (errno == ENOENT) return SIEVEDIR_OK;
83
84 xsyslog(LOG_ERR, "IOERROR: can not open sieve directory",
85 "path=<%s>", sievedir);
86 return SIEVEDIR_IOERROR;
87 }
88
89 dir_len = snprintf(path, sizeof(path), "%s/", sievedir);
90
91 while ((dir = readdir(dp)) != NULL) {
92 const char *name = dir->d_name;
93 char target[PATH_MAX] = "";
94 struct stat sbuf;
95
96 if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
97
98 strlcpy(path + dir_len, name, sizeof(path) - dir_len);
99
100 if (lstat(path, &sbuf) < 0) continue;
101
102 if (S_ISREG(sbuf.st_mode)) {
103 if (flags) {
104 const char *ext = strrchr(name, '.');
105
106 if (!ext || strcmp(ext, SCRIPT_SUFFIX)) {
107 if (flags & SIEVEDIR_SCRIPTS_ONLY) {
108 /* ignore non-scripts */
109 continue;
110 }
111 if ((flags & SIEVEDIR_IGNORE_JUNK) &&
112 strcmpnull(ext, BYTECODE_SUFFIX)) {
113 /* ignore non-bytecode */
114 continue;
115 }
116 }
117 }
118 }
119 else if (flags & SIEVEDIR_SCRIPTS_ONLY) {
120 /* ignore all other entries */
121 continue;
122 }
123 else if (S_ISLNK(sbuf.st_mode)) {
124 /* fetch link target */
125 ssize_t tgt_len = readlink(path, target, sizeof(target) - 1);
126
127 if (tgt_len > 0) target[tgt_len] = '\0';
128 }
129 else if (flags & SIEVEDIR_IGNORE_JUNK) {
130 /* ignore all other entries */
131 continue;
132 }
133
134 r = func(sievedir, name, &sbuf, target, rock);
135 if (r != SIEVEDIR_OK) break;
136 }
137
138 closedir(dp);
139
140 return r;
141 }
142
143 struct count_rock {
144 int count;
145 const char *myname;
146 };
147
count_cb(const char * sievedir,const char * name,struct stat * sbuf,const char * link_target,void * rock)148 static int count_cb(const char *sievedir __attribute__((unused)),
149 const char *name,
150 struct stat *sbuf __attribute__((unused)),
151 const char *link_target __attribute__((unused)),
152 void *rock)
153 {
154 struct count_rock *crock = (struct count_rock *) rock;
155 size_t name_len = strlen(name) - SCRIPT_SUFFIX_LEN;
156
157 if (!crock->myname ||
158 strlen(crock->myname) != name_len ||
159 strncmp(crock->myname, name, name_len)) {
160 /* and it's different from me */
161 crock->count++;
162 }
163
164 return SIEVEDIR_OK;
165 }
166
167 /* counts the number of scripts user has that are DIFFERENT from name */
sievedir_num_scripts(const char * sievedir,const char * name)168 EXPORTED int sievedir_num_scripts(const char *sievedir, const char *name)
169 {
170 struct count_rock crock = { 0, name };
171
172 sievedir_foreach(sievedir, SIEVEDIR_SCRIPTS_ONLY, &count_cb, &crock);
173
174 return crock.count;
175 }
176
177
sievedir_get_script(const char * sievedir,const char * script)178 EXPORTED struct buf *sievedir_get_script(const char *sievedir,
179 const char *script)
180 {
181 struct buf buf = BUF_INITIALIZER;
182
183 buf_printf(&buf, "%s/%s", sievedir, script);
184
185 int fd = open(buf_cstring(&buf), 0);
186 buf_free(&buf);
187 if (fd < 0) return NULL;
188
189 buf_refresh_mmap(&buf, 1, fd, script, MAP_UNKNOWN_LEN, "sieve");
190
191 close(fd);
192
193 struct buf *ret = buf_new();
194
195 buf_move(ret, &buf);
196
197 return ret;
198 }
199
200 /* Everything but '/' and '\0' are valid. */
sievedir_valid_name(const struct buf * name)201 EXPORTED int sievedir_valid_name(const struct buf *name)
202 {
203 size_t lup, len = buf_len(name);
204 const char *ptr;
205
206 /* must be at least one character long */
207 if (len < 1) return 0;
208
209 ptr = buf_base(name);
210
211 for (lup = 0; lup < len; lup++) {
212 if ((ptr[lup] == '/') || (ptr[lup] == '\0')) return 0;
213 }
214
215 return (lup < SIEVEDIR_MAX_NAME_LEN);
216 }
217
sievedir_script_exists(const char * sievedir,const char * name)218 EXPORTED int sievedir_script_exists(const char *sievedir, const char *name)
219 {
220 char path[PATH_MAX];
221 struct stat sbuf;
222
223 snprintf(path, sizeof(path), "%s/%s%s", sievedir, name, SCRIPT_SUFFIX);
224
225 return ((stat(path, &sbuf) == 0) && S_ISREG(sbuf.st_mode));
226 }
227
sievedir_get_active(const char * sievedir)228 EXPORTED const char *sievedir_get_active(const char *sievedir)
229 {
230 static char target[PATH_MAX];
231 char link[PATH_MAX];
232 ssize_t tgt_len;
233
234 snprintf(link, sizeof(link), "%s/%s", sievedir, DEFAULTBC_NAME);
235
236 tgt_len = readlink(link, target, sizeof(target) - 1);
237
238 if (tgt_len > BYTECODE_SUFFIX_LEN) {
239 target[tgt_len - BYTECODE_SUFFIX_LEN] = '\0';
240 return target;
241 }
242 else if (tgt_len == -1 && errno != ENOENT) {
243 xsyslog(LOG_ERR, "IOERROR: failed to read active script link",
244 "link=<%s>", link);
245 }
246
247 return NULL;
248 }
249
sievedir_script_isactive(const char * sievedir,const char * name)250 EXPORTED int sievedir_script_isactive(const char *sievedir, const char *name)
251 {
252 if (!name) return 0;
253
254 return (strcmpnull(name, sievedir_get_active(sievedir)) == 0);
255 }
256
sievedir_activate_script(const char * sievedir,const char * name)257 EXPORTED int sievedir_activate_script(const char *sievedir, const char *name)
258 {
259 char target[PATH_MAX];
260 char active[PATH_MAX];
261 char tmp[PATH_MAX+4]; /* +4 for ".NEW" */
262
263 if (sievedir_script_isactive(sievedir, name)) {
264 /* already active - nothing to do here */
265 return SIEVEDIR_OK;
266 }
267
268 snprintf(target, sizeof(target), "%s%s", name, BYTECODE_SUFFIX);
269 snprintf(active, sizeof(active), "%s/%s", sievedir, DEFAULTBC_NAME);
270 snprintf(tmp, sizeof(tmp), "%s.NEW", active);
271
272 /* N.B symlink() does NOT verify target for anything but string validity,
273 * so activation of a nonexistent script will report success.
274 */
275 if (symlink(target, tmp) < 0) {
276 xsyslog(LOG_ERR, "IOERROR: failed to create temp active script link",
277 "target=<%s> link=<%s>", target, tmp);
278 return SIEVEDIR_IOERROR;
279 }
280
281 if (rename(tmp, active) < 0) {
282 xsyslog(LOG_ERR, "IOERROR: failed to rename active script link",
283 "oldpath=<%s> newpath=<%s>", tmp, active);
284 unlink(tmp);
285 return SIEVEDIR_IOERROR;
286 }
287
288 return SIEVEDIR_OK;
289 }
290
sievedir_deactivate_script(const char * sievedir)291 EXPORTED int sievedir_deactivate_script(const char *sievedir)
292 {
293 char active[PATH_MAX];
294
295 snprintf(active, sizeof(active), "%s/defaultbc", sievedir);
296 if (unlink(active) != 0 && errno != ENOENT) {
297 xsyslog(LOG_ERR, "IOERROR: failed to delete active script link",
298 "link=<%s>", active);
299 return SIEVEDIR_IOERROR;
300 }
301
302 return SIEVEDIR_OK;
303 }
304
sievedir_delete_script(const char * sievedir,const char * name)305 EXPORTED int sievedir_delete_script(const char *sievedir, const char *name)
306 {
307 char path[PATH_MAX];
308 int r;
309
310 /* delete bytecode first, as its non-deterministic */
311 snprintf(path, sizeof(path), "%s/%s%s", sievedir, name, BYTECODE_SUFFIX);
312 r = unlink(path);
313 if (r && errno != ENOENT) {
314 xsyslog(LOG_ERR, "IOERROR: failed to delete bytecode file",
315 "path=<%s>", path);
316 }
317
318 /* delete script file last, which determines result */
319 snprintf(path, sizeof(path), "%s/%s%s", sievedir, name, SCRIPT_SUFFIX);
320 r = unlink(path);
321 if (r) {
322 if (errno == ENOENT) return SIEVEDIR_NOTFOUND;
323
324 xsyslog(LOG_ERR, "IOERROR: failed to delete script file",
325 "path=<%s>", path);
326 return SIEVEDIR_IOERROR;
327 }
328
329 return SIEVEDIR_OK;
330 }
331
sievedir_rename_script(const char * sievedir,const char * oldname,const char * newname)332 EXPORTED int sievedir_rename_script(const char *sievedir,
333 const char *oldname, const char *newname)
334 {
335 /* rename script and bytecode; move active link */
336 char oldpath[PATH_MAX], newpath[PATH_MAX];
337 int r;
338
339 snprintf(oldpath, sizeof(oldpath),
340 "%s/%s%s", sievedir, oldname, SCRIPT_SUFFIX);
341 snprintf(newpath, sizeof(oldpath),
342 "%s/%s%s", sievedir, newname, SCRIPT_SUFFIX);
343 r = rename(oldpath, newpath);
344 if (r) {
345 if (errno == ENOENT) return SIEVEDIR_NOTFOUND;
346
347 xsyslog(LOG_ERR, "IOERROR: failed to rename script file",
348 "oldpath=<%s> newpath=<%s>", oldpath, newpath);
349 return SIEVEDIR_IOERROR;
350 }
351
352 snprintf(oldpath, sizeof(oldpath),
353 "%s/%s%s", sievedir, oldname, BYTECODE_SUFFIX);
354 snprintf(newpath, sizeof(newpath),
355 "%s/%s%s", sievedir, newname, BYTECODE_SUFFIX);
356 r = rename(oldpath, newpath);
357 if (r) {
358 xsyslog(LOG_ERR, "IOERROR: failed to rename bytecode file",
359 "oldpath=<%s> newpath=<%s>", oldpath, newpath);
360 return SIEVEDIR_IOERROR;
361 }
362
363 if (sievedir_script_isactive(sievedir, oldname)) {
364 r = sievedir_activate_script(sievedir, newname);
365 }
366
367 return r;
368 }
369
370 #ifdef USE_SIEVE
sievedir_put_script(const char * sievedir,const char * name,const char * content,char ** errors)371 EXPORTED int sievedir_put_script(const char *sievedir, const char *name,
372 const char *content, char **errors)
373 {
374 char new_path[PATH_MAX];
375 FILE *f;
376
377 /* parse the script */
378 sieve_script_t *s = NULL;
379 (void) sieve_script_parse_string(NULL, content, errors, &s);
380 if (!s) return SIEVEDIR_INVALID;
381
382 /* open a new file for the script */
383 snprintf(new_path, sizeof(new_path),
384 "%s/%s%s.NEW", sievedir, name, SCRIPT_SUFFIX);
385
386 f = fopen(new_path, "w+");
387
388 if (f == NULL) {
389 xsyslog(LOG_ERR, "IOERROR: failed to open new script file",
390 "newpath=<%s>", new_path);
391 sieve_script_free(&s);
392 return SIEVEDIR_IOERROR;
393 }
394
395 size_t i, content_len = strlen(content);
396 int saw_cr = 0;
397
398 /* copy data to file - replacing any lone CR or LF with the
399 * CRLF pair so notify messages are SMTP compatible */
400 for (i = 0; i < content_len; i++) {
401 if (saw_cr) {
402 if (content[i] != '\n') putc('\n', f);
403 }
404 else if (content[i] == '\n')
405 putc('\r', f);
406
407 putc(content[i], f);
408 saw_cr = (content[i] == '\r');
409 }
410 if (saw_cr) putc('\n', f);
411
412 fflush(f);
413 fclose(f);
414
415 /* generate the bytecode */
416 bytecode_info_t *bc = NULL;
417 if (sieve_generate_bytecode(&bc, s) == -1) {
418 unlink(new_path);
419 sieve_script_free(&s);
420 return SIEVEDIR_FAIL;
421 }
422
423 /* open the new bytecode file */
424 char new_bcpath[PATH_MAX];
425 snprintf(new_bcpath, sizeof(new_bcpath),
426 "%s/%s%s.NEW", sievedir, name, BYTECODE_SUFFIX);
427 int fd = open(new_bcpath, O_CREAT | O_TRUNC | O_WRONLY, 0600);
428 if (fd < 0) {
429 xsyslog(LOG_ERR, "IOERROR: failed to open new bytecode file",
430 "newpath=<%s>", new_bcpath);
431 unlink(new_path);
432 sieve_free_bytecode(&bc);
433 sieve_script_free(&s);
434 return SIEVEDIR_IOERROR;
435 }
436
437 /* emit the bytecode */
438 if (sieve_emit_bytecode(fd, bc) == -1) {
439 close(fd);
440 unlink(new_path);
441 unlink(new_bcpath);
442 sieve_free_bytecode(&bc);
443 sieve_script_free(&s);
444 return SIEVEDIR_FAIL;
445 }
446
447 sieve_free_bytecode(&bc);
448 sieve_script_free(&s);
449
450 close(fd);
451
452 /* rename */
453 char path[PATH_MAX];
454 snprintf(path, sizeof(path), "%s/%s%s", sievedir, name, SCRIPT_SUFFIX);
455 int r = rename(new_path, path);
456 if (r) {
457 xsyslog(LOG_ERR, "IOERROR: failed to rename script file",
458 "oldpath=<%s> newpath=<%s>", new_path, path);
459 }
460 else {
461 snprintf(path, sizeof(path), "%s/%s%s", sievedir, name, BYTECODE_SUFFIX);
462 r = rename(new_bcpath, path);
463 if (r) {
464 xsyslog(LOG_ERR, "IOERROR: failed to rename bytecode file",
465 "oldpath=<%s> newpath=<%s>", new_bcpath, path);
466 }
467 }
468
469 return (r ? SIEVEDIR_IOERROR : SIEVEDIR_OK);
470 }
471 #endif /* USE_SIEVE */
472