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