fdm-1.7+cvs20140912/0000700000175000017500000000000012404656672012211 5ustar hawkhawkfdm-1.7+cvs20140912/xmalloc-debug.c0000600000175000017500000001271510651756377015115 0ustar hawkhawk/* $Id: xmalloc-debug.c,v 1.22 2007/07/25 23:30:07 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef DEBUG #include #include #include #include #include #include #include "fdm.h" /* Single xmalloc allocated block. */ struct xmalloc_blk { void *caller; void *ptr; size_t size; SPLAY_ENTRY(xmalloc_blk) entry; }; /* Splay tree of allocated blocks. */ SPLAY_HEAD(xmalloc_tree, xmalloc_blk); struct xmalloc_tree xmalloc_tree = SPLAY_INITIALIZER(&xmalloc_tree); /* Various statistics. */ size_t xmalloc_allocated; size_t xmalloc_freed; size_t xmalloc_peak; u_int xmalloc_frees; u_int xmalloc_mallocs; u_int xmalloc_reallocs; /* Print function. */ #define XMALLOC_PRINT log_debug3 /* Bytes of unallocated blocks and number of allocated blocks to show. */ #define XMALLOC_BYTES 8 #define XMALLOC_LINES 32 /* Macro to update peek usage variable. */ #define XMALLOC_UPDATE() do { \ if (xmalloc_allocated - xmalloc_freed > xmalloc_peak) \ xmalloc_peak = xmalloc_allocated - xmalloc_freed; \ } while (0) /* Tree functions. */ int xmalloc_cmp(struct xmalloc_blk *, struct xmalloc_blk *); SPLAY_PROTOTYPE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp); SPLAY_GENERATE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp); /* Compare two blocks. */ int xmalloc_cmp(struct xmalloc_blk *blk1, struct xmalloc_blk *blk2) { uintptr_t ptr1 = (uintptr_t) blk1->ptr; uintptr_t ptr2 = (uintptr_t) blk2->ptr; if (ptr1 < ptr2) return (-1); if (ptr1 > ptr2) return (1); return (0); } /* Clear statistics and block list; used to start fresh after fork(2). */ void xmalloc_clear(void) { struct xmalloc_blk *blk; xmalloc_allocated = 0; xmalloc_freed = 0; xmalloc_peak = 0; xmalloc_frees = 0; xmalloc_mallocs = 0; xmalloc_reallocs = 0; while (!SPLAY_EMPTY(&xmalloc_tree)) { blk = SPLAY_ROOT(&xmalloc_tree); SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); free(blk); } } /* Print report of statistics and unfreed blocks. */ void xmalloc_report(pid_t pid, const char *hdr) { struct xmalloc_blk *blk; u_char *iptr; char buf[4 * XMALLOC_BYTES + 1], *optr; size_t len; u_int n; Dl_info info; XMALLOC_PRINT("%s: %ld: allocated=%zu, freed=%zu, difference=%zd, " "peak=%zu", hdr, (long) pid, xmalloc_allocated, xmalloc_freed, xmalloc_allocated - xmalloc_freed, xmalloc_peak); XMALLOC_PRINT("%s: %ld: mallocs=%u, reallocs=%u, frees=%u", hdr, (long) pid, xmalloc_mallocs, xmalloc_reallocs, xmalloc_frees); n = 0; SPLAY_FOREACH(blk, xmalloc_tree, &xmalloc_tree) { n++; if (n >= XMALLOC_LINES) continue; len = blk->size; if (len > XMALLOC_BYTES) len = XMALLOC_BYTES; memset(&info, 0, sizeof info); if (dladdr(blk->caller, &info) == 0) info.dli_sname = info.dli_saddr = NULL; optr = buf; iptr = blk->ptr; for (; len > 0; len--) { if (isascii(*iptr) && !iscntrl(*iptr)) { *optr++ = *iptr++; continue; } *optr++ = '\\'; *optr++ = '0' + ((*iptr >> 6) & 07); *optr++ = '0' + ((*iptr >> 3) & 07); *optr++ = '0' + (*iptr & 07); iptr++; } *optr = '\0'; XMALLOC_PRINT("%s: %ld: %u, %s+0x%02tx: [%p %zu: %s]", hdr, (long) pid, n, info.dli_sname, ((u_char *) blk->caller) - ((u_char *) info.dli_saddr), blk->ptr, blk->size, buf); } XMALLOC_PRINT("%s: %ld: %u unfreed blocks", hdr, (long) pid, n); } /* Record a newly created block. */ void xmalloc_new(void *caller, void *ptr, size_t size) { struct xmalloc_blk *blk; xmalloc_allocated += size; XMALLOC_UPDATE(); if ((blk = malloc(sizeof *blk)) == NULL) abort(); blk->ptr = ptr; blk->size = size; blk->caller = caller; SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk); xmalloc_mallocs++; XMALLOC_UPDATE(); } /* Record changes to a block. */ void xmalloc_change(void *caller, void *oldptr, void *newptr, size_t newsize) { struct xmalloc_blk *blk, key; ssize_t change; if (oldptr == NULL) { xmalloc_new(caller, newptr, newsize); return; } key.ptr = oldptr; blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key); if (blk == NULL) return; change = newsize - blk->size; if (change > 0) xmalloc_allocated += change; else xmalloc_freed -= change; XMALLOC_UPDATE(); SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); blk->ptr = newptr; blk->size = newsize; blk->caller = caller; SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk); xmalloc_reallocs++; XMALLOC_UPDATE(); } /* Record a block free. */ void xmalloc_free(void *ptr) { struct xmalloc_blk *blk, key; key.ptr = ptr; blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key); if (blk == NULL) return; xmalloc_freed += blk->size; SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); free(blk); xmalloc_frees++; XMALLOC_UPDATE(); } #endif /* DEBUG */ fdm-1.7+cvs20140912/deliver-smtp.c0000600000175000017500000001246011154774311014765 0ustar hawkhawk/* $Id: deliver-smtp.c,v 1.58 2009/03/08 16:56:41 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" int deliver_smtp_deliver(struct deliver_ctx *, struct actitem *); void deliver_smtp_desc(struct actitem *, char *, size_t); int deliver_smtp_code(char *); struct deliver deliver_smtp = { "smtp", DELIVER_ASUSER, deliver_smtp_deliver, deliver_smtp_desc }; int deliver_smtp_code(char *line) { char ch; const char *errstr; int n; size_t len; len = strspn(line, "0123456789"); if (len == 0) return (-1); ch = line[len]; line[len] = '\0'; n = strtonum(line, 100, 999, &errstr); line[len] = ch; if (errstr != NULL) return (-1); return (n); } int deliver_smtp_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_smtp_data *data = ti->data; int done, code; struct io *io; char *cause, *to, *from, *line, *ptr, *lbuf; enum deliver_smtp_state state; size_t len, llen; io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (DELIVER_FAILURE); } if (conf.debug > 3 && !conf.syslog) io->dup_fd = STDOUT_FILENO; llen = IO_LINESIZE; lbuf = xmalloc(llen); if (conf.host_fqdn != NULL) xasprintf(&ptr, "%s@%s", dctx->udata->name, conf.host_fqdn); else xasprintf(&ptr, "%s@%s", dctx->udata->name, conf.host_name); if (data->to.str == NULL) to = xstrdup(ptr); else { to = replacestr(&data->to, m->tags, m, &m->rml); if (to == NULL || *to == '\0') { xasprintf(&cause, "%s: empty to", a->name); from = NULL; goto error; } } if (data->from.str == NULL) from = xstrdup(ptr); else { from = replacestr(&data->from, m->tags, m, &m->rml); if (from == NULL || *from == '\0') { xasprintf(&cause, "%s: empty from", a->name); goto error; } } xfree(ptr); state = SMTP_CONNECTING; line = NULL; done = 0; do { switch (io_pollline2(io, &line, &lbuf, &llen, conf.timeout, &cause)) { case 0: cause = xstrdup("connection unexpectedly closed"); goto error; case -1: goto error; } code = deliver_smtp_code(line); cause = NULL; switch (state) { case SMTP_CONNECTING: if (code != 220) goto error; state = SMTP_HELO; if (conf.host_fqdn != NULL) io_writeline(io, "HELO %s", conf.host_fqdn); else io_writeline(io, "HELO %s", conf.host_name); break; case SMTP_HELO: if (code != 250) goto error; state = SMTP_FROM; io_writeline(io, "MAIL FROM:<%s>", from); break; case SMTP_FROM: if (code != 250) goto error; state = SMTP_TO; io_writeline(io, "RCPT TO:<%s>", to); break; case SMTP_TO: if (code != 250) goto error; state = SMTP_DATA; io_writeline(io, "DATA"); break; case SMTP_DATA: if (code != 354) goto error; line_init(m, &ptr, &len); while (ptr != NULL) { if (len > 1) { if (*ptr == '.') io_write(io, ".", 1); io_write(io, ptr, len - 1); } io_writeline(io, NULL); /* Update if necessary. */ if (io_update(io, conf.timeout, &cause) != 1) goto error; line_next(m, &ptr, &len); } state = SMTP_DONE; io_writeline(io, "."); io_flush(io, conf.timeout, NULL); break; case SMTP_DONE: if (code != 250) goto error; state = SMTP_QUIT; io_writeline(io, "QUIT"); break; case SMTP_QUIT: /* * Exchange sometimes refuses to accept QUIT as a valid * command, but since we got a 250 the mail has been * accepted. So, allow 500 here too. */ if (code != 500 && code != 221) goto error; done = 1; break; } } while (!done); xfree(lbuf); xfree(from); xfree(to); io_close(io); io_free(io); return (DELIVER_SUCCESS); error: if (cause != NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); } else log_warnx("%s: unexpected response: %s", a->name, line); io_writeline(io, "QUIT"); io_flush(io, conf.timeout, NULL); xfree(lbuf); if (from != NULL) xfree(from); if (to != NULL) xfree(to); io_close(io); io_free(io); return (DELIVER_FAILURE); } void deliver_smtp_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_smtp_data *data = ti->data; xsnprintf(buf, len, "smtp%s server \"%s\" port %s to \"%s\"", data->server.ssl ? "s" : "", data->server.host, data->server.port, data->to.str); } fdm-1.7+cvs20140912/deliver-remove-from-cache.c0000600000175000017500000000473210700413673017301 0ustar hawkhawk/* $Id: deliver-remove-from-cache.c,v 1.1 2007/10/02 10:04:43 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_remove_from_cache_deliver( struct deliver_ctx *, struct actitem *); void deliver_remove_from_cache_desc(struct actitem *, char *, size_t); struct deliver deliver_remove_from_cache = { "remove-from-cache", DELIVER_INCHILD, deliver_remove_from_cache_deliver, deliver_remove_from_cache_desc }; int deliver_remove_from_cache_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_remove_from_cache_data *data = ti->data; char *key; struct cache *cache; key = replacestr(&data->key, m->tags, m, &m->rml); if (key == NULL || *key == '\0') { log_warnx("%s: empty key", a->name); goto error; } log_debug2("%s: removing from cache %s: %s", a->name, data->path, key); TAILQ_FOREACH(cache, &conf.caches, entry) { if (strcmp(data->path, cache->path) == 0) { if (open_cache(a, cache) != 0) goto error; if (db_contains(cache->db, key) && db_remove(cache->db, key) != 0) { log_warnx( "%s: error removing from cache %s: %s", a->name, cache->path, key); goto error; } xfree(key); return (DELIVER_SUCCESS); } } log_warnx("%s: cache %s not declared", a->name, data->path); error: if (key != NULL) xfree(key); return (DELIVER_FAILURE); } void deliver_remove_from_cache_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_remove_from_cache_data *data = ti->data; xsnprintf(buf, len, "remove-from-cache \"%s\" key \"%s\"", data->path, data->key.str); } fdm-1.7+cvs20140912/Makefile0000600000175000017500000000334512227440104013640 0ustar hawkhawk# $Id: Makefile,v 1.184 2013/10/16 07:29:08 nicm Exp $ .SUFFIXES: .c .o .PHONY: clean regress VERSION= 1.7 FDEBUG= 1 CC?= cc YACC= yacc -d CPPFLAGS+= -I/usr/local/include -I. CFLAGS+= -DBUILD="\"$(VERSION)\"" LDFLAGS+= -L/usr/local/lib LIBS+= -lssl -lcrypto -ltdb -lz .ifdef FDEBUG LDFLAGS+= -Wl,-E CFLAGS+= -g -ggdb -DDEBUG CFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 CFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations CFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare CFLAGS+= -Wundef -Wbad-function-cast -Winline -Wcast-align .endif .ifdef COURIER CFLAGS+= -DLOOKUP_COURIER LIBS+= -lcourierauth .endif .ifdef PCRE CFLAGS+= -DPCRE LIBS+= -lpcre .endif PREFIX?= /usr/local INSTALLDIR= install -d INSTALLBIN= install -g bin -o root -m 555 INSTALLMAN= install -g bin -o root -m 444 SRCS!= echo *.c|sed 's|y.tab.c||g'; echo y.tab.c .include "config.mk" OBJS= ${SRCS:S/.c/.o/} .c.o: ${CC} ${CPPFLAGS} ${CFLAGS} -c ${.IMPSRC} -o ${.TARGET} all: fdm lex.o: y.tab.c y.tab.c: parse.y ${YACC} parse.y fdm: ${OBJS} ${CC} ${LDFLAGS} -o fdm ${OBJS} ${LIBS} depend: mkdep ${CPPFLAGS} ${CFLAGS} ${SRCS:M*.c} clean: rm -f fdm *.o .depend *~ *.core *.log compat/*.o y.tab.[ch] clean-all: clean rm -f config.h config.mk regress: fdm cd regress && ${MAKE} install: all ${INSTALLDIR} ${DESTDIR}${PREFIX}/bin ${INSTALLBIN} fdm ${DESTDIR}${PREFIX}/bin/ ${INSTALLDIR} ${DESTDIR}${PREFIX}/man/man1 ${INSTALLMAN} fdm.1 ${DESTDIR}${PREFIX}/man/man1/ ${INSTALLDIR} ${DESTDIR}${PREFIX}/man/man5 ${INSTALLMAN} fdm.conf.5 ${DESTDIR}${PREFIX}/man/man5/ uninstall: rm -f ${DESTDIR}${PREFIX}/bin/fdm rm -f ${DESTDIR}${PREFIX}/man/man1/fdm.1 rm -f ${DESTDIR}${PREFIX}/man/man5/fdm.conf.5 fdm-1.7+cvs20140912/imap-common.c0000600000175000017500000005413211673716067014602 0ustar hawkhawk/* $Id: imap-common.c,v 1.87 2011/12/19 20:19:03 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" void imap_free(void *); int imap_parse(struct account *, int, char *); char *imap_base64_encode(char *); char *imap_base64_decode(char *); int imap_state_connect(struct account *, struct fetch_ctx *); int imap_state_capability1(struct account *, struct fetch_ctx *); int imap_state_capability2(struct account *, struct fetch_ctx *); int imap_state_cram_md5_auth(struct account *, struct fetch_ctx *); int imap_state_login(struct account *, struct fetch_ctx *); int imap_state_user(struct account *, struct fetch_ctx *); int imap_state_pass(struct account *, struct fetch_ctx *); int imap_state_select2(struct account *, struct fetch_ctx *); int imap_state_select3(struct account *, struct fetch_ctx *); int imap_state_select4(struct account *, struct fetch_ctx *); int imap_state_search1(struct account *, struct fetch_ctx *); int imap_state_search2(struct account *, struct fetch_ctx *); int imap_state_search3(struct account *, struct fetch_ctx *); int imap_state_next(struct account *, struct fetch_ctx *); int imap_state_body(struct account *, struct fetch_ctx *); int imap_state_line(struct account *, struct fetch_ctx *); int imap_state_mail(struct account *, struct fetch_ctx *); int imap_state_commit(struct account *, struct fetch_ctx *); int imap_state_expunge(struct account *, struct fetch_ctx *); int imap_state_close(struct account *, struct fetch_ctx *); int imap_state_quit(struct account *, struct fetch_ctx *); /* Put line to server. */ int imap_putln(struct account *a, const char *fmt, ...) { struct fetch_imap_data *data = a->data; va_list ap; int n; va_start(ap, fmt); n = data->putln(a, fmt, ap); va_end(ap); return (n); } /* * Get line from server. Returns -1 on error, 0 on success, a NULL line when * out of data. */ int imap_getln(struct account *a, struct fetch_ctx *fctx, int type, char **line) { struct fetch_imap_data *data = a->data; int n; do { if (data->getln(a, fctx, line) != 0) return (-1); if (*line == NULL) return (0); } while ((n = imap_parse(a, type, *line)) == 1); return (n); } /* Free auxiliary data. */ void imap_free(void *ptr) { xfree(ptr); } /* Check for okay from server. */ int imap_okay(char *line) { char *ptr; ptr = strchr(line, ' '); if (ptr == NULL) return (0); if (ptr[1] != 'O' || ptr[2] != 'K' || (ptr[3] != ' ' && ptr[3] != '\0')) return (0); return (1); } /* Check for no from server. */ int imap_no(char *line) { char *ptr; ptr = strchr(line, ' '); if (ptr == NULL) return (0); if (ptr[1] != 'N' || ptr[2] != 'O' || (ptr[3] != ' ' && ptr[3] != '\0')) return (0); return (1); } /* * Parse line based on type. Returns -1 on error, 0 on success, 1 to ignore * this line. */ int imap_parse(struct account *a, int type, char *line) { struct fetch_imap_data *data = a->data; int tag; if (type == IMAP_RAW) return (0); tag = imap_tag(line); switch (type) { case IMAP_TAGGED: if (tag == IMAP_TAG_NONE) return (1); if (tag == IMAP_TAG_CONTINUE) goto invalid; if (tag != data->tag) goto invalid; break; case IMAP_UNTAGGED: if (tag != IMAP_TAG_NONE) goto invalid; break; case IMAP_CONTINUE: if (tag == IMAP_TAG_NONE) return (1); if (tag != IMAP_TAG_CONTINUE) goto invalid; break; } return (0); invalid: imap_bad(a, line); return (-1); } /* Parse IMAP tag. */ int imap_tag(char *line) { int tag; const char *errstr; char *ptr; if (line[0] == '*' && line[1] == ' ') return (IMAP_TAG_NONE); if (line[0] == '+') return (IMAP_TAG_CONTINUE); if ((ptr = strchr(line, ' ')) == NULL) ptr = strchr(line, '\0'); *ptr = '\0'; tag = strtonum(line, 0, INT_MAX, &errstr); *ptr = ' '; if (errstr != NULL) return (IMAP_TAG_ERROR); return (tag); } /* Base64 encode string. */ char * imap_base64_encode(char *in) { char *out; size_t size; size = (strlen(in) * 2) + 1; out = xcalloc(1, size); if (b64_ntop(in, strlen(in), out, size) < 0) { xfree(out); return (NULL); } return (out); } /* Base64 decode string. */ char * imap_base64_decode(char *in) { char *out; size_t size; size = (strlen(in) * 4) + 1; out = xcalloc(1, size); if (b64_pton(in, out, size) < 0) { xfree(out); return (NULL); } return (out); } int imap_bad(struct account *a, const char *line) { log_warnx("%s: unexpected data: %s", a->name, line); return (FETCH_ERROR); } int imap_invalid(struct account *a, const char *line) { log_warnx("%s: invalid response: %s", a->name, line); return (FETCH_ERROR); } /* Commit mail. */ int imap_commit(struct account *a, struct mail *m) { struct fetch_imap_data *data = a->data; struct fetch_imap_mail *aux = m->auxdata; if (m->decision == DECISION_DROP) ARRAY_ADD(&data->dropped, aux->uid); else ARRAY_ADD(&data->kept, aux->uid); xfree(aux); m->auxdata = m->auxfree = NULL; return (FETCH_AGAIN); } /* Abort fetch. */ void imap_abort(struct account *a) { struct fetch_imap_data *data = a->data; ARRAY_FREE(&data->dropped); ARRAY_FREE(&data->kept); ARRAY_FREE(&data->wanted); data->disconnect(a); } /* Return total mails available. */ u_int imap_total(struct account *a) { struct fetch_imap_data *data = a->data; return (data->folders_total); } /* Common initialisation state. */ int imap_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; ARRAY_INIT(&data->dropped); ARRAY_INIT(&data->kept); ARRAY_INIT(&data->wanted); data->tag = 0; data->folder = 0; data->folders_total = 0; fctx->state = imap_state_connect; return (FETCH_AGAIN); } /* Connect state. */ int imap_state_connect(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; if (data->connect(a) != 0) return (FETCH_ERROR); fctx->state = imap_state_connected; return (FETCH_BLOCK); } /* Connected state: wait for initial line from server. */ int imap_state_connected(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (strncmp(line, "* PREAUTH", 9) == 0) { fctx->state = imap_state_select1; return (FETCH_AGAIN); } if (data->user == NULL || data->pass == NULL) { log_warnx("%s: not PREAUTH and no user or password", a->name); return (FETCH_ERROR); } if (imap_putln(a, "%u CAPABILITY", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_capability1; return (FETCH_BLOCK); } /* Capability state 1. Parse capabilities and set flags. */ int imap_state_capability1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); /* Convert to uppercase. */ for (ptr = line; *ptr != '\0'; ptr++) *ptr = toupper((u_char) *ptr); if (strstr(line, "IMAP4REV1") == NULL) { log_warnx("%s: no IMAP4rev1 capability: %s", a->name, line); return (FETCH_ERROR); } data->capa = 0; if (strstr(line, "AUTH=CRAM-MD5") != NULL) data->capa |= IMAP_CAPA_AUTH_CRAM_MD5; /* Use XYZZY to detect Google brokenness. */ if (strstr(line, "XYZZY") != NULL) data->capa |= IMAP_CAPA_XYZZY; fctx->state = imap_state_capability2; return (FETCH_AGAIN); } /* Capability state 2. Check capabilities and choose login type. */ int imap_state_capability2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); /* Try CRAM-MD5, if server supports it and user allows it. */ if (!data->nocrammd5 && (data->capa & IMAP_CAPA_AUTH_CRAM_MD5)) { if (imap_putln(a, "%u AUTHENTICATE CRAM-MD5", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_cram_md5_auth; return (FETCH_BLOCK); } /* Fall back to LOGIN, unless config disallows it. */ if (!data->nologin) { if (imap_putln(a, "%u LOGIN {%zu}", ++data->tag, strlen(data->user)) != 0) return (FETCH_ERROR); fctx->state = imap_state_login; return (FETCH_BLOCK); } log_warnx("%s: no authentication methods", a->name); return (FETCH_ERROR); } /* CRAM-MD5 auth state. */ int imap_state_cram_md5_auth(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr, *src, *b64; char out[EVP_MAX_MD_SIZE * 2 + 1]; u_char digest[EVP_MAX_MD_SIZE]; u_int i, n; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); ptr = line + 1; while (isspace((u_char) *ptr)) ptr++; if (*ptr == '\0') return (imap_invalid(a, line)); b64 = imap_base64_decode(ptr); HMAC(EVP_md5(), data->pass, strlen(data->pass), b64, strlen(b64), digest, &n); xfree(b64); for (i = 0; i < n; i++) xsnprintf(out + i * 2, 3, "%02hhx", digest[i]); xasprintf(&src, "%s %s", data->user, out); b64 = imap_base64_encode(src); xfree(src); if (imap_putln(a, "%s", b64) != 0) { xfree(b64); return (FETCH_ERROR); } xfree(b64); fctx->state = imap_state_pass; return (FETCH_BLOCK); } /* Login state. */ int imap_state_login(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (imap_putln(a, "%s {%zu}", data->user, strlen(data->pass)) != 0) return (FETCH_ERROR); fctx->state = imap_state_user; return (FETCH_BLOCK); } /* User state. */ int imap_state_user(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (imap_putln(a, "%s", data->pass) != 0) return (FETCH_ERROR); fctx->state = imap_state_pass; return (FETCH_BLOCK); } /* Pass state. */ int imap_state_pass(struct account *a, struct fetch_ctx *fctx) { char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); fctx->state = imap_state_select1; return (FETCH_AGAIN); } /* Select state 1. */ int imap_state_select1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; if (imap_putln(a, "%u SELECT {%zu}", ++data->tag, strlen(ARRAY_ITEM(data->folders, data->folder))) != 0) return (FETCH_ERROR); fctx->state = imap_state_select2; return (FETCH_BLOCK); } /* Select state 2. Wait for continuation and send folder name. */ int imap_state_select2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_CONTINUE, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (imap_putln(a, "%s", ARRAY_ITEM(data->folders, data->folder)) != 0) return (FETCH_ERROR); fctx->state = imap_state_select3; return (FETCH_BLOCK); } /* Select state 3. Hold until select returns message count. */ int imap_state_select3(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; for (;;) { if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "* %u EXISTS", &data->total) == 1) break; } fctx->state = imap_state_select4; return (FETCH_AGAIN); } /* Select state 4. Hold until select completes. */ int imap_state_select4(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); /* If no mails, stop early. */ if (data->total == 0) { if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } fctx->state = imap_state_search1; return (FETCH_AGAIN); } /* Search state 1. Request list of mail required. */ int imap_state_search1(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; /* Search for a list of the mail UIDs to fetch. */ switch (data->only) { case FETCH_ONLY_NEW: if (imap_putln(a, "%u UID SEARCH UNSEEN", ++data->tag) != 0) return (FETCH_ERROR); break; case FETCH_ONLY_OLD: if (imap_putln(a, "%u UID SEARCH SEEN", ++data->tag) != 0) return (FETCH_ERROR); break; default: if (imap_putln(a, "%u UID SEARCH ALL", ++data->tag) != 0) return (FETCH_ERROR); break; } fctx->state = imap_state_search2; return (FETCH_BLOCK); } /* Search state 2. */ int imap_state_search2(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line, *ptr; u_int uid; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); /* Skip the header. */ if (strncasecmp(line, "* SEARCH", 8) != 0) return (imap_bad(a, line)); line += 8; /* Read each UID and save it. */ do { while (isspace((u_char) *line)) line++; ptr = strchr(line, ' '); if (ptr == NULL) ptr = strchr(line, '\0'); if (ptr == line) break; if (sscanf(line, "%u", &uid) != 1) return (imap_bad(a, line)); ARRAY_ADD(&data->wanted, uid); log_debug3("%s: fetching UID: %u", a->name, uid); line = ptr; } while (*line == ' '); fctx->state = imap_state_search3; return (FETCH_AGAIN); } /* Search state 3. */ int imap_state_search3(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); /* Save the total. */ data->total = ARRAY_LENGTH(&data->wanted); /* Update grand total. */ data->folders_total += data->total; /* If no mails, or polling, stop here. */ if (data->total == 0 || fctx->flags & FETCH_POLL) { if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } fctx->state = imap_state_next; return (FETCH_AGAIN); } /* * Next state. Get next mail. This is also the idle state when completed, so * check for finished mail, exiting, and so on. */ int imap_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; /* Handle dropped and kept mail. */ if (!ARRAY_EMPTY(&data->dropped)) { if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Deleted)", ++data->tag, ARRAY_FIRST(&data->dropped)) != 0) return (FETCH_ERROR); ARRAY_REMOVE(&data->dropped, 0); fctx->state = imap_state_commit; return (FETCH_BLOCK); } if (!ARRAY_EMPTY(&data->kept)) { /* * GMail is broken and does not set the \Seen flag after mail * is fetched, so set it explicitly for kept mail. */ if (imap_putln(a, "%u UID STORE %u +FLAGS.SILENT (\\Seen)", ++data->tag, ARRAY_FIRST(&data->kept)) != 0) return (FETCH_ERROR); ARRAY_REMOVE(&data->kept, 0); fctx->state = imap_state_commit; return (FETCH_BLOCK); } /* Need to purge, switch to purge state. */ if (fctx->flags & FETCH_PURGE) { /* * If can't purge now, loop through this state until there is * no mail on the dropped queue and FETCH_EMPTY is set. Can't * have a seperate state to loop through without returning * here: mail could potentially be added to the dropped list * while in that state. */ if (fctx->flags & FETCH_EMPTY) { fctx->flags &= ~FETCH_PURGE; if (imap_putln(a, "%u EXPUNGE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_expunge; return (FETCH_BLOCK); } /* * Must be waiting for delivery, so permit blocking even though * we (fetch) aren't waiting for any data. */ return (FETCH_BLOCK); } /* If last mail, wait for everything to be committed then close down. */ if (ARRAY_EMPTY(&data->wanted)) { if (data->committed != data->total) return (FETCH_BLOCK); if (imap_putln(a, "%u CLOSE", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_close; return (FETCH_BLOCK); } /* Fetch the next mail. */ if (imap_putln(a, "%u " "UID FETCH %u BODY[]",++data->tag, ARRAY_FIRST(&data->wanted)) != 0) return (FETCH_ERROR); fctx->state = imap_state_body; return (FETCH_BLOCK); } /* Body state. */ int imap_state_body(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; struct fetch_imap_mail *aux; char *line, *ptr; u_int n; if (imap_getln(a, fctx, IMAP_UNTAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "* %u FETCH (", &n) != 1) return (imap_invalid(a, line)); if ((ptr = strstr(line, "BODY[] {")) == NULL) return (imap_invalid(a, line)); if (sscanf(ptr, "BODY[] {%zu}", &data->size) != 1) return (imap_invalid(a, line)); data->lines = 0; /* Fill in local data. */ aux = xcalloc(1, sizeof *aux); aux->uid = ARRAY_FIRST(&data->wanted); m->auxdata = aux; m->auxfree = imap_free; ARRAY_REMOVE(&data->wanted, 0); /* Open the mail. */ if (mail_open(m, data->size) != 0) { log_warnx("%s: failed to create mail", a->name); return (FETCH_ERROR); } m->size = 0; /* Tag mail. */ default_tags(&m->tags, data->src); if (data->server.host != NULL) { add_tag(&m->tags, "server", "%s", data->server.host); add_tag(&m->tags, "port", "%s", data->server.port); } add_tag(&m->tags, "server_uid", "%u", aux->uid); add_tag(&m->tags, "folder", "%s", ARRAY_ITEM(data->folders, data->folder)); /* If we already know the mail is oversize, start off flushing it. */ data->flushing = data->size > conf.max_size; fctx->state = imap_state_line; return (FETCH_AGAIN); } /* Line state. */ int imap_state_line(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; struct mail *m = fctx->mail; char *line; size_t used, size, left; for (;;) { if (imap_getln(a, fctx, IMAP_RAW, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (data->flushing) continue; /* Check if this line would exceed the expected size. */ used = m->size + data->lines; size = strlen(line); if (used + size + 2 > data->size) break; if (append_line(m, line, size) != 0) { log_warnx("%s: failed to resize mail", a->name); return (FETCH_ERROR); } data->lines++; } /* * Calculate the number of bytes still needed. The current line must * include at least that much data. Some servers include UID or FLAGS * after the message: we don't care about these so just ignore them and * make sure there is a terminating ). */ left = data->size - used; if (line[size - 1] != ')' && size <= left) return (imap_invalid(a, line)); /* If there was data left, add it as a new line without trailing \n. */ if (left > 0) { if (append_line(m, line, left) != 0) { log_warnx("%s: failed to resize mail", a->name); return (FETCH_ERROR); } data->lines++; /* Wipe out the trailing \n. */ m->size--; } fctx->state = imap_state_mail; return (FETCH_AGAIN); } /* Mail state. */ int imap_state_mail(struct account *a, struct fetch_ctx *fctx) { char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); fctx->state = imap_state_next; return (FETCH_MAIL); } /* Commit state. */ int imap_state_commit(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); data->committed++; fctx->state = imap_state_next; return (FETCH_AGAIN); } /* Expunge state. */ int imap_state_expunge(struct account *a, struct fetch_ctx *fctx) { char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); fctx->state = imap_state_next; return (FETCH_AGAIN); } /* Close state. */ int imap_state_close(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); data->folder++; if (data->folder != ARRAY_LENGTH(data->folders)) { fctx->state = imap_state_select1; return (FETCH_AGAIN); } if (imap_putln(a, "%u LOGOUT", ++data->tag) != 0) return (FETCH_ERROR); fctx->state = imap_state_quit; return (FETCH_BLOCK); } /* Quit state. */ int imap_state_quit(struct account *a, struct fetch_ctx *fctx) { char *line; if (imap_getln(a, fctx, IMAP_TAGGED, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!imap_okay(line)) return (imap_bad(a, line)); imap_abort(a); return (FETCH_EXIT); } fdm-1.7+cvs20140912/fdm.h0000600000175000017500000005606712142176241013134 0ustar hawkhawk/* $Id: fdm.h,v 1.352 2013/05/07 13:07:45 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef FDM_H #define FDM_H #include "config.h" #include #include #include #ifdef HAVE_QUEUE_H #include #else #include "compat/queue.h" #endif #ifdef HAVE_TREE_H #include #else #include "compat/tree.h" #endif #include #include #include #include #ifndef _PUBLIC_ #define _PUBLIC_ #endif #include #include #ifdef PCRE #include #endif #include #include #define CHILDUSER "_fdm" #define CONFFILE ".fdm.conf" #define SYSCONFFILE "/etc/fdm.conf" #define LOCKFILE ".fdm.lock" #define SYSLOCKFILE "/var/run/fdm.lock" #define MAXQUEUEVALUE 50 #define DEFMAILQUEUE 2 #define DEFMAILSIZE (32 * 1024 * 1024) /* 32 MB */ #define MAXMAILSIZE (1 * 1024 * 1024 * 1024) /* 1 GB */ #define DEFSTRIPCHARS "\\<>$%^&*|{}[]\"'`;" #define MAXACTIONCHAIN 5 #define DEFTIMEOUT (900 * 1000) #define LOCKSLEEPTIME 10000 /* 0.1 seconds */ #define LOCKTOTALTIME 10000000 /* 10 seconds */ #define MAXNAMESIZE 64 #define DEFUMASK (S_IRWXG|S_IRWXO) #define FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) #define DIRMODE (S_IRWXU|S_IRWXG|S_IRWXO) #define MAXUSERLEN 256 extern char *__progname; /* Linux compatibility bullshit. */ #ifndef UID_MAX #define UID_MAX UINT_MAX #endif #ifndef GID_MAX #define GID_MAX UINT_MAX #endif #ifndef INFTIM #define INFTIM -1 #endif #ifndef __dead #define __dead __attribute__ ((__noreturn__)) #endif #ifndef __packed #define __packed __attribute__ ((__packed__)) #endif /* Databases are not portable between endiness on OSs without these. */ #ifndef htole64 #define htole64 #endif #ifndef letoh64 #define letoh64 #endif #ifdef DEBUG #define NFDS 64 #define COUNTFDS(s) do { \ int fd_i, fd_n; \ fd_n = 0; \ for (fd_i = 0; fd_i < NFDS; fd_i++) { \ if (fcntl(fd_i, F_GETFL) != -1) \ fd_n++; \ } \ log_debug2("%s: %d file descriptors in use", s, fd_n); \ } while (0) #endif /* Fatal errors. */ #define fatal(msg) log_fatal("%s: %s", __func__, msg); #define fatalx(msg) log_fatalx("%s: %s", __func__, msg); /* Apply umask. */ #define UMASK(mask) ((mask) & ~conf.file_umask) /* Convert a file mode for %o%o%o printf. */ #define MODE(m) \ (m & S_IRUSR ? 4 : 0) + (m & S_IWUSR ? 2 : 0) + (m & S_IXUSR ? 1 : 0), \ (m & S_IRGRP ? 4 : 0) + (m & S_IWGRP ? 2 : 0) + (m & S_IXGRP ? 1 : 0), \ (m & S_IROTH ? 4 : 0) + (m & S_IWOTH ? 2 : 0) + (m & S_IXOTH ? 1 : 0) /* Definition to shut gcc up about unused arguments. */ #define unused __attribute__ ((unused)) /* Attribute to make gcc check printf-like arguments. */ #define printflike1 __attribute__ ((format (printf, 1, 2))) #define printflike2 __attribute__ ((format (printf, 2, 3))) #define printflike3 __attribute__ ((format (printf, 3, 4))) #define printflike4 __attribute__ ((format (printf, 4, 5))) #define printflike5 __attribute__ ((format (printf, 5, 6))) #define printflike6 __attribute__ ((format (printf, 6, 7))) /* Ensure buffer size. */ #define ENSURE_SIZE(buf, len, size) do { \ (buf) = ensure_size(buf, &(len), 1, size); \ } while (0) #define ENSURE_FOR(buf, len, size, adj) do { \ (buf) = ensure_for(buf, &(len), size, adj); \ } while (0) /* Description buffer size. */ #define DESCBUFSIZE 512 /* Replace buffer size. */ #define REPLBUFSIZE 64 /* Lengths of time. */ #define TIME_MINUTE 60LL #define TIME_HOUR 3600LL #define TIME_DAY 86400LL #define TIME_WEEK 604800LL #define TIME_MONTH 2419200LL #define TIME_YEAR 29030400LL /* Number of matches to use. */ #define NPMATCH 10 /* Account and action name match. */ #define account_match(p, n) (fnmatch(p, n, 0) == 0) #define action_match(p, n) (fnmatch(p, n, 0) == 0) #include "array.h" #include "io.h" /* Macros in configuration file. */ struct macro { char name[MAXNAMESIZE]; union { long long num; char *str; } value; enum { MACRO_NUMBER, MACRO_STRING } type; TAILQ_ENTRY(macro) entry; }; TAILQ_HEAD(macros, macro); /* Command-line commands. */ enum fdmop { FDMOP_NONE = 0, FDMOP_POLL, FDMOP_FETCH, FDMOP_CACHE }; /* * Wrapper struct for a string that needs tag replacement before it is used. * This is used for anything that needs to be replaced after account and mail * data are available, everything else is replaced at parse time. */ struct replstr { char *str; } __packed; ARRAY_DECL(replstrs, struct replstr); /* Similar to replstr but needs expand_path too. */ struct replpath { char *str; } __packed; /* Server description. */ struct server { char *host; char *port; struct addrinfo *ai; int ssl; int tls1; int verify; }; /* Proxy type. */ enum proxytype { PROXY_HTTP, PROXY_HTTPS, PROXY_SOCKS5 }; /* Proxy definition. */ struct proxy { enum proxytype type; char *user; char *pass; struct server server; }; /* Shared memory. */ struct shm { char name[MAXNAMLEN]; int fd; #define SHM_REGISTER(shm) cleanup_register(shm_path(shm)) #define SHM_DEREGISTER(shm) cleanup_deregister(shm_path(shm)) void *data; size_t size; }; /* Generic array of strings. */ ARRAY_DECL(strings, char *); /* Options for final mail handling. */ enum decision { DECISION_NONE, DECISION_DROP, DECISION_KEEP }; /* String block entry. */ struct strbent { size_t key; size_t value; }; /* String block header. */ struct strb { u_int ent_used; u_int ent_max; size_t str_used; size_t str_size; }; /* Initial string block slots and block size. */ #define STRBOFFSET (((sizeof (struct strb)) + 0x3f) & ~0x3f) #define STRBENTRIES 64 #define STRBBLOCK 1024 /* String block access macros. */ #define STRB_BASE(sb) (((char *) (sb)) + STRBOFFSET) #define STRB_KEY(sb, sbe) (STRB_BASE(sb) + (sbe)->key) #define STRB_VALUE(sb, sbe) (STRB_BASE(sb) + (sbe)->value) #define STRB_ENTBASE(sb) (STRB_BASE(sb) + (sb)->str_size) #define STRB_ENTOFF(sb, n) ((n) * (sizeof (struct strbent))) #define STRB_ENTSIZE(sb) STRB_ENTOFF(sb, (sb)->ent_max) #define STRB_ENTRY(sb, n) ((void *) (STRB_ENTBASE(sb) + STRB_ENTOFF(sb, n))) #define STRB_SIZE(sb) (STRBOFFSET + (sb)->str_size + STRB_ENTSIZE((sb))) /* Regexp wrapper structs. */ struct re { char *str; #ifndef PCRE regex_t re; #else pcre *pcre; #endif int flags; }; struct rm { int valid; size_t so; size_t eo; }; struct rmlist { int valid; struct rm list[NPMATCH]; }; /* Regexp flags. */ #define RE_IGNCASE 0x1 #define RE_NOSUBST 0x2 /* Cache data. */ struct cache { TDB_CONTEXT *db; char *path; uint64_t expire; TAILQ_ENTRY(cache) entry; }; struct cacheitem { uint64_t tim; uint32_t pad[4]; } __packed; /* A single mail. */ struct mail { u_int idx; double tim; struct strb *tags; struct shm shm; struct attach *attach; int attach_built; char *base; char *data; size_t off; size_t size; /* size of mail */ size_t space; /* size of allocated area */ size_t body; /* offset of body */ ARRAY_DECL(, size_t) wrapped; /* list of wrapped lines */ char wrapchar; /* wrapped character */ /* XXX move below into special struct and just cp it in mail_*? */ struct rmlist rml; /* regexp matches */ enum decision decision; /* final deliver decision */ void (*auxfree)(void *); void *auxdata; }; /* Mail match/delivery return codes. */ #define MAIL_CONTINUE 0 #define MAIL_DELIVER 1 #define MAIL_MATCH 2 #define MAIL_ERROR 3 #define MAIL_BLOCKED 4 #define MAIL_DONE 5 /* Mail match/delivery context. */ struct mail_ctx { int done; u_int msgid; struct account *account; struct io *io; struct mail *mail; u_int ruleidx; struct rule *rule; ARRAY_DECL(, struct rule *) stack; struct expritem *expritem; int result; int matched; TAILQ_HEAD(, deliver_ctx) dqueue; TAILQ_ENTRY(mail_ctx) entry; }; TAILQ_HEAD(mail_queue, mail_ctx); /* An attachment. */ struct attach { u_int idx; size_t data; size_t body; size_t size; char *type; char *name; struct attach *parent; TAILQ_HEAD(, attach) children; TAILQ_ENTRY(attach) entry; }; /* Privsep message types. */ enum msgtype { MSG_ACTION, MSG_EXIT, MSG_DONE, MSG_COMMAND }; /* Privsep message data. */ struct msgdata { int error; struct mail mail; /* These only work so long as they aren't moved in either process. */ struct account *account; struct actitem *actitem; struct match_command_data *cmddata; uid_t uid; gid_t gid; }; /* Privsep message buffer. */ struct msgbuf { void *buf; size_t len; }; /* Privsep message. */ struct msg { u_int id; enum msgtype type; size_t size; struct msgdata data; }; /* A single child. */ struct child { pid_t pid; struct child *parent; struct io *io; void *data; int (*msg)(struct child *, struct msg *, struct msgbuf *); void *buf; size_t len; }; /* List of children. */ ARRAY_DECL(children, struct child *); /* Fetch child data. */ struct child_fetch_data { struct account *account; enum fdmop op; struct children *children; }; /* Deliver child data. */ struct child_deliver_data { void (*hook)(int, struct account *, struct msg *, struct child_deliver_data *, int *); struct child *child; /* the source of the request */ uid_t uid; gid_t gid; u_int msgid; const char *name; struct account *account; struct mail *mail; struct actitem *actitem; struct deliver_ctx *dctx; struct mail_ctx *mctx; struct match_command_data *cmddata; }; /* Account entry. */ struct account { u_int idx; char name[MAXNAMESIZE]; struct replstrs *users; int disabled; int keep; struct fetch *fetch; void *data; TAILQ_ENTRY(account) entry; TAILQ_ENTRY(account) active_entry; }; /* Action item. */ struct actitem { u_int idx; struct deliver *deliver; void *data; TAILQ_ENTRY(actitem) entry; }; /* Action list. */ TAILQ_HEAD(actlist, actitem); /* Action definition. */ struct action { char name[MAXNAMESIZE]; struct replstrs *users; struct actlist *list; TAILQ_ENTRY(action) entry; }; /* Actions arrays. */ ARRAY_DECL(actions, struct action *); /* Match areas. */ enum area { AREA_BODY, AREA_HEADERS, AREA_ANY }; /* Expression operators. */ enum exprop { OP_NONE, OP_AND, OP_OR }; /* Expression item. */ struct expritem { struct match *match; void *data; enum exprop op; int inverted; TAILQ_ENTRY(expritem) entry; }; /* Expression struct. */ TAILQ_HEAD(expr, expritem); /* Rule list. */ TAILQ_HEAD(rules, rule); /* Rule entry. */ struct rule { u_int idx; struct expr *expr; struct replstrs *users; int stop; /* stop matching at this rule */ struct rules rules; struct action *lambda; struct replstrs *actions; TAILQ_ENTRY(rule) entry; }; /* Lock types. */ #define LOCK_FCNTL 0x1 #define LOCK_FLOCK 0x2 #define LOCK_DOTLOCK 0x4 /* User info settings. */ struct userdata { char *name; char *home; uid_t uid; gid_t gid; }; /* User lookup order. */ typedef struct userdata *(*userfunction)(const char *); ARRAY_DECL(userfunctions, userfunction); /* Configuration settings. */ struct conf { int debug; int syslog; uid_t child_uid; gid_t child_gid; char *tmp_dir; struct strings incl; struct strings excl; struct proxy *proxy; char *user_home; struct userfunctions *user_order; char *host_name; char *host_fqdn; char *host_address; char *conf_file; char *lock_file; char *strip_chars; int check_only; int allow_many; int keep_all; int no_received; int no_create; int verify_certs; u_int purge_after; enum decision impl_act; int max_accts; int queue_high; int queue_low; mode_t file_umask; gid_t file_group; size_t max_size; int timeout; int del_big; u_int lock_types; char *def_user; char *cmd_user; TAILQ_HEAD(, cache) caches; TAILQ_HEAD(, account) accounts; TAILQ_HEAD(, action) actions; struct rules rules; }; extern struct conf conf; /* Command flags. */ #define CMD_IN 0x1 #define CMD_OUT 0x2 #define CMD_ONCE 0x4 /* Command data. */ struct cmd { pid_t pid; int status; int flags; const char *buf; size_t len; struct io *io_in; struct io *io_out; struct io *io_err; }; /* Comparison operators. */ enum cmp { CMP_EQ, CMP_NE, CMP_LT, CMP_GT }; /* Configuration file (used by parser). */ struct file { FILE *f; int line; const char *path; }; ARRAY_DECL(files, struct file *); #ifndef HAVE_SETRESUID #define setresuid(r, e, s) setreuid(r, e) #endif #ifndef HAVE_SETRESGID #define setresgid(r, e, s) setregid(r, e) #endif #ifndef HAVE_STRTONUM /* strtonum.c */ long long strtonum(const char *, long long, long long, const char **); #endif #ifndef HAVE_STRLCPY /* strlcpy.c */ size_t strlcpy(char *, const char *, size_t); #endif #ifndef HAVE_STRLCAT /* strlcat.c */ size_t strlcat(char *, const char *, size_t); #endif /* shm.c */ char *shm_path(struct shm *); void *shm_create(struct shm *, size_t); int shm_owner(struct shm *, uid_t, gid_t); void shm_destroy(struct shm *); void shm_close(struct shm *); void *shm_reopen(struct shm *); void *shm_resize(struct shm *, size_t, size_t); /* lex.c */ int yylex(void); /* parse.y */ extern struct macros parse_macros; extern struct files parse_filestack; extern struct file *parse_file; extern struct strb *parse_tags; int parse_conf(const char *, struct strings *); __dead printflike1 void yyerror(const char *, ...); /* parse-fn.c */ char *expand_path(const char *, const char *); char *run_command(const char *, const char *); char *fmt_replstrs(const char *, struct replstrs *); char *fmt_strings(const char *, struct strings *); int have_accounts(char *); struct account *find_account(char *); struct action *find_action(char *); struct actions *match_actions(const char *); struct macro *extract_macro(char *); struct macro *find_macro(const char *); void find_netrc(const char *, char **, char **); int find_netrc1(const char *, char **, char **, char **); void free_account(struct account *); void free_action(struct action *); void free_actitem(struct actitem *); void free_cache(struct cache *); void free_replstrs(struct replstrs *); void free_rule(struct rule *); void free_strings(struct strings *); void make_actlist(struct actlist *, char *, size_t); void print_action(struct action *); void print_rule(struct rule *); /* netrc.c */ FILE *netrc_open(const char *, char **); void netrc_close(FILE *); int netrc_lookup(FILE *, const char *, char **, char **); /* fdm.c */ extern volatile sig_atomic_t sigusr1; extern volatile sig_atomic_t sigint; extern volatile sig_atomic_t sigterm; double get_time(void); void dropto(uid_t, gid_t); int check_incl(const char *); int check_excl(const char *); int use_account(struct account *, char **); void fill_host(void); __dead void usage(void); /* cache-op.c */ __dead void cache_op(int, char **); /* re.c */ int re_compile(struct re *, const char *, int, char **); int re_string(struct re *, const char *, struct rmlist *, char **); int re_block(struct re *, const void *, size_t, struct rmlist *, char **); void re_free(struct re *); /* attach.c */ struct attach *attach_visit(struct attach *, u_int *); void printflike2 attach_log(struct attach *, const char *, ...); struct attach *attach_build(struct mail *); void attach_free(struct attach *); /* lookup.c */ struct userdata *user_lookup(const char *, struct userfunctions *); void user_free(struct userdata *); struct userdata *user_copy(struct userdata *); /* lookup-passwd.c */ struct userdata *passwd_lookup(const char *); #ifdef LOOKUP_COURIER /* lookup-courier.c */ struct userdata *courier_lookup(const char *); #endif /* privsep.c */ int privsep_send(struct io *, struct msg *, struct msgbuf *); int privsep_check(struct io *); int privsep_recv(struct io *, struct msg *, struct msgbuf *); /* command.c */ struct cmd *cmd_start(const char *, int, const char *, size_t, char **); int cmd_poll(struct cmd *, char **, char **, char **, size_t *, int, char **); void cmd_free(struct cmd *); /* child.c */ int child_fork(void); __dead void child_exit(int); struct child *child_start(struct children *, uid_t, gid_t, int (*)(struct child *, struct io *), int (*)(struct child *, struct msg *, struct msgbuf *), void *, struct child *); /* child-fetch.c */ int open_cache(struct account *, struct cache *); int child_fetch(struct child *, struct io *); /* child-deliver.c */ int child_deliver(struct child *, struct io *); void child_deliver_action_hook(int, struct account *, struct msg *, struct child_deliver_data *, int *); void child_deliver_cmd_hook(int, struct account *, struct msg *, struct child_deliver_data *, int *); /* parent-fetch.c */ int parent_fetch(struct child *, struct msg *, struct msgbuf *); /* parent-deliver.c */ int parent_deliver(struct child *, struct msg *, struct msgbuf *); /* timer.c */ int timer_expired(void); void timer_set(int); void timer_cancel(void); /* connect.c */ char *sslerror(const char *); char *sslerror2(int, const char *); void getaddrs(const char *, char **, char **); struct proxy *getproxy(const char *); struct io *connectproxy(struct server *, int, struct proxy *, const char *, int, char **); struct io *connectio(struct server *, int, const char *, int, char **); /* file.c */ int printflike3 ppath(char *, size_t, const char *, ...); int vppath(char *, size_t, const char *, va_list); int openlock(const char *, int, u_int); int createlock(const char *, int, uid_t, gid_t, mode_t, u_int); void closelock(int, const char *, u_int); int locksleep(const char *, const char *, long long *); int xcreate(const char *, int, uid_t, gid_t, mode_t); int xmkdir(const char *, uid_t, gid_t, mode_t); int xmkpath(const char *, uid_t, gid_t, mode_t); const char *checkmode(struct stat *, mode_t); const char *checkowner(struct stat *, uid_t); const char *checkgroup(struct stat *, gid_t); int safemove(const char *, const char *); /* mail.c */ int mail_open(struct mail *, size_t); void mail_send(struct mail *, struct msg *); int mail_receive(struct mail *, struct msg *, int); void mail_close(struct mail *); void mail_destroy(struct mail *); int mail_resize(struct mail *, size_t); void line_init(struct mail *, char **, size_t *); void line_next(struct mail *, char **, size_t *); int printflike3 insert_header(struct mail *, const char *, const char *, ...); int remove_header(struct mail *, const char *); char *find_header(struct mail *, const char *, size_t *, int); char *match_header(struct mail *, const char *, size_t *, int); size_t find_body(struct mail *); void count_lines(struct mail *, u_int *, u_int *); int append_line(struct mail *, const char *, size_t); char *find_address(char *, size_t, size_t *); void trim_from(struct mail *); char *make_from(struct mail *, char *); u_int fill_wrapped(struct mail *); void set_wrapped(struct mail *, char); /* mail-time.c */ char *rfc822time(time_t, char *, size_t); int mailtime(struct mail *, time_t *); /* mail-state.c */ int mail_match(struct mail_ctx *, struct msg *, struct msgbuf *); int mail_deliver(struct mail_ctx *, struct msg *, struct msgbuf *); /* db-tdb.c */ TDB_CONTEXT *db_open(char *); void db_close(TDB_CONTEXT *); int db_add(TDB_CONTEXT *, char *); int db_remove(TDB_CONTEXT *, char *); int db_contains(TDB_CONTEXT *, char *); int db_size(TDB_CONTEXT *); int db_print(TDB_CONTEXT *, void (*)(const char *, ...)); int db_expire(TDB_CONTEXT *, uint64_t); int db_clear(TDB_CONTEXT *); /* cleanup.c */ void cleanup_check(void); void cleanup_flush(void); void cleanup_purge(void); void cleanup_register(const char *); void cleanup_deregister(const char *); /* strb.c */ void strb_create(struct strb **); void strb_clear(struct strb **); void strb_destroy(struct strb **); void strb_dump(struct strb *, const char *, void (*)(const char *, ...)); void printflike3 strb_add(struct strb **, const char *, const char *, ...); void strb_vadd(struct strb **, const char *, const char *, va_list); struct strbent *strb_find(struct strb *, const char *); struct strbent *strb_match(struct strb *, const char *); /* replace.c */ void printflike3 add_tag(struct strb **, const char *, const char *, ...); const char *find_tag(struct strb *, const char *); const char *match_tag(struct strb *, const char *); void default_tags(struct strb **, const char *); void update_tags(struct strb **, struct userdata *); void reset_tags(struct strb **); char *replacestr(struct replstr *, struct strb *, struct mail *, struct rmlist *); char *replacepath(struct replpath *, struct strb *, struct mail *, struct rmlist *, const char *); /* log.c */ #define LOG_FACILITY LOG_MAIL void log_open_syslog(int); void log_open_tty(int); void log_open_file(int, const char *); void log_close(void); void log_vwrite(int, const char *, va_list); void log_write(int, const char *, ...); void printflike1 log_warn(const char *, ...); void printflike1 log_warnx(const char *, ...); void printflike1 log_info(const char *, ...); void printflike1 log_debug(const char *, ...); void printflike1 log_debug2(const char *, ...); void printflike1 log_debug3(const char *, ...); __dead void log_vfatal(const char *, va_list); __dead void log_fatal(const char *, ...); __dead void log_fatalx(const char *, ...); /* xmalloc.c */ void *ensure_size(void *, size_t *, size_t, size_t); void *ensure_for(void *, size_t *, size_t, size_t); char *xstrdup(const char *); void *xcalloc(size_t, size_t); void *xmalloc(size_t); void *xrealloc(void *, size_t, size_t); void xfree(void *); int printflike2 xasprintf(char **, const char *, ...); int xvasprintf(char **, const char *, va_list); int printflike3 xsnprintf(char *, size_t, const char *, ...); int xvsnprintf(char *, size_t, const char *, va_list); int printflike3 printpath(char *, size_t, const char *, ...); char *xdirname(const char *); char *xbasename(const char *); /* xmalloc-debug.c */ #ifdef DEBUG #define xmalloc_caller() __builtin_return_address(0) void xmalloc_clear(void); void xmalloc_report(pid_t, const char *); void xmalloc_new(void *, void *, size_t); void xmalloc_change(void *, void *, void *, size_t); void xmalloc_free(void *); #endif #endif /* FDM_H */ fdm-1.7+cvs20140912/match-matched.c0000600000175000017500000000262410577566340015063 0ustar hawkhawk/* $Id: match-matched.c,v 1.8 2007/03/19 20:04:48 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_matched_match(struct mail_ctx *, struct expritem *); void match_matched_desc(struct expritem *, char *, size_t); struct match match_matched = { "matched", match_matched_match, match_matched_desc }; int match_matched_match(struct mail_ctx *mctx, unused struct expritem *ei) { if (mctx->matched) return (MATCH_TRUE); return (MATCH_FALSE); } void match_matched_desc(unused struct expritem *ei, char *buf, size_t len) { strlcpy(buf, "matched", len); } fdm-1.7+cvs20140912/parse.y0000600000175000017500000015720212142176241013512 0ustar hawkhawk/* $Id: parse.y,v 1.274 2013/05/07 13:07:45 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* Declarations */ %{ #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" #include "fetch.h" #include "match.h" struct strb *parse_tags; struct macros parse_macros; struct macro *parse_last; /* last command-line argument macro */ u_int parse_ruleidx; u_int parse_actionidx; ARRAY_DECL(, struct rule *) parse_rulestack; struct rule *parse_rule; struct files parse_filestack; struct file *parse_file; int yyparse(void); int parse_conf(const char *path, struct strings *macros) { struct macro *macro; FILE *f; u_int i; if ((f = fopen(path, "r")) == NULL) return (-1); ARRAY_INIT(&parse_rulestack); parse_rule = NULL; ARRAY_INIT(&parse_filestack); parse_file = xmalloc(sizeof *parse_file); parse_file->f = f; parse_file->line = 0; parse_file->path = path; strb_create(&parse_tags); default_tags(&parse_tags, NULL); add_tag(&parse_tags, "home", "%s", conf.user_home); TAILQ_INIT(&parse_macros); parse_last = NULL; for (i = 0; i < ARRAY_LENGTH(macros); i++) { parse_last = extract_macro(ARRAY_ITEM(macros, i)); TAILQ_INSERT_TAIL(&parse_macros, parse_last, entry); } parse_file->line++; yyparse(); if (!ARRAY_EMPTY(&parse_rulestack)) yyerror("missing }"); ARRAY_FREE(&parse_rulestack); ARRAY_FREE(&parse_filestack); xfree(parse_file); while (!TAILQ_EMPTY(&parse_macros)) { macro = TAILQ_FIRST(&parse_macros); TAILQ_REMOVE(&parse_macros, macro, entry); if (macro->type == MACRO_STRING) xfree(macro->value.str); xfree(macro); } strb_destroy(&parse_tags); fclose(f); return (0); } __dead printflike1 void yyerror(const char *fmt, ...) { va_list ap; char *s; xasprintf(&s, "%s: %s at line %d", parse_file->path, fmt, parse_file->line); va_start(ap, fmt); log_vwrite(LOG_CRIT, s, ap); va_end(ap); exit(1); } %} %token TOKACCOUNT %token TOKACCOUNTS %token TOKACTION %token TOKACTIONS %token TOKADDHEADER %token TOKADDTOCACHE %token TOKAGE %token TOKALL %token TOKALLOWMANY %token TOKAND %token TOKANYNAME %token TOKANYSIZE %token TOKANYTYPE %token TOKAPPEND %token TOKATTACHMENT %token TOKBODY %token TOKBYTES %token TOKCACHE %token TOKCASE %token TOKCMDUSER %token TOKCOMPRESS %token TOKCONTINUE %token TOKCOUNT %token TOKCOURIER %token TOKDAYS %token TOKDEFUSER %token TOKDELTOOBIG %token TOKDISABLED %token TOKDOMAIN %token TOKDOTLOCK %token TOKDROP %token TOKEQ %token TOKEXEC %token TOKEXPIRE %token TOKFCNTL %token TOKFILEGROUP %token TOKFILEUMASK %token TOKFLOCK %token TOKFOLDER %token TOKFOLDERS %token TOKFROM %token TOKGIGABYTES %token TOKGROUP %token TOKGROUPS %token TOKHEADER %token TOKHEADERS %token TOKHOURS %token TOKIMAP %token TOKIMAPS %token TOKIMPLACT %token TOKIN %token TOKINCACHE %token TOKINVALID %token TOKKEEP %token TOKKEY %token TOKKILOBYTES %token TOKLOCKFILE %token TOKLOCKTYPES %token TOKLOOKUPORDER %token TOKMAILDIR %token TOKMAILDIRS %token TOKMATCH %token TOKMATCHED %token TOKMAXSIZE %token TOKMBOX %token TOKMBOXES %token TOKMEGABYTES %token TOKMINUTES %token TOKMONTHS %token TOKNE %token TOKNEWONLY %token TOKNNTP %token TOKNNTPS %token TOKNOAPOP %token TOKNOCRAMMD5 %token TOKNOCREATE %token TOKNOLOGIN %token TOKNONE %token TOKNORECEIVED %token TOKNOT %token TOKNOTLS1 %token TOKNOUIDL %token TOKNOVERIFY %token TOKOLDONLY %token TOKOR %token TOKPARALLELACCOUNTS %token TOKPASS %token TOKPASSWD %token TOKPIPE %token TOKPOP3 %token TOKPOP3S %token TOKPORT %token TOKPROXY %token TOKPURGEAFTER %token TOKQUEUEHIGH %token TOKQUEUELOW %token TOKREMOVEFROMCACHE %token TOKREMOVEHEADER %token TOKREMOVEHEADERS %token TOKRETURNS %token TOKREWRITE %token TOKSECONDS %token TOKSERVER %token TOKSET %token TOKSIZE %token TOKSMTP %token TOKSTDIN %token TOKSTDOUT %token TOKSTRING %token TOKSTRIPCHARACTERS %token TOKTAG %token TOKTAGGED %token TOKTIMEOUT %token TOKTO %token TOKTOTALSIZE %token TOKUNMATCHED %token TOKUSER %token TOKUSERS %token TOKVALUE %token TOKVERIFYCERTS %token TOKWEEKS %token TOKWRITE %token TOKYEARS %union { long long number; char *string; int flag; u_int locks; struct { struct fetch *fetch; void *data; } fetch; struct { char *host; char *port; } server; enum area area; enum exprop exprop; struct actitem *actitem; struct actlist *actlist; struct expr *expr; struct expritem *expritem; struct strings *strings; struct replstrs *replstrs; enum fetch_only only; struct { char *path; enum fetch_only only; } poponly; struct { int flags; char *str; } re; gid_t localgid; enum cmp cmp; struct rule *rule; struct { char *user; int user_netrc; char *pass; int pass_netrc; } userpass; userfunction ufn; struct userfunctions *ufns; } %token NONE %token NUMBER %token STRING STRMACRO NUMMACRO %token STRCOMMAND NUMCOMMAND %type actitem %type actlist %type area %type cmp ltgt eqne %type expr exprlist %type expritem %type exprop %type fetchtype %type cont not disabled keep execpipe writeappend compress verify tls1 %type apop poptype imaptype nntptype nocrammd5 nologin uidl %type localgid %type lock locklist %type size time numv retrc expire %type only imaponly %type poponly %type replstrslist actions rmheaders accounts users %type casere retre %type perform %type server %type port to from xstrv strv replstrv replpathv val optval folder1 %type user %type stringslist pathslist maildirs mboxes groups folders folderlist %type userpass userpassreqd userpassnetrc %type ufn %type ufnlist %% /* Rules */ /** CMDS */ cmds: /* empty */ | cmds account | cmds defaction | cmds defmacro | cmds rule | cmds set | cmds close | cmds cache | cmds NONE /* Plural/singular combinations. */ /** ACTIONP */ actionp: TOKACTION | TOKACTIONS /** USERP */ userp: TOKUSER | TOKUSERS /** ACCOUNTP */ accountp: TOKACCOUNT | TOKACCOUNTS /** GROUPP */ groupp: TOKGROUP | TOKGROUPS /** FOLDERP */ folderp: TOKFOLDER | TOKFOLDERS /** MAILDIRP */ maildirp: TOKMAILDIR | TOKMAILDIRS /** MBOXP */ mboxp: TOKMBOX | TOKMBOXES /** RMHEADERP */ rmheaderp: TOKREMOVEHEADER | TOKREMOVEHEADERS /** VAL: (char *) */ val: TOKVALUE strv /** [$2: strv (char *)] */ { $$ = $2; } | strv /** [$1: strv (char *)] */ { $$ = $1; } /** OPTVAL: (char *) */ optval: TOKVALUE strv /** [$2: strv (char *)] */ { $$ = $2; } | /* empty */ { $$ = NULL; } /** XSTRV: (char *) */ xstrv: STRCOMMAND { $$ = run_command($1, parse_file->path); xfree($1); } | STRING { $$ = $1; } | STRMACRO { struct macro *macro; if (strlen($1) > MAXNAMESIZE) yyerror("macro name too long: %s", $1); if ((macro = find_macro($1)) == NULL) yyerror("undefined macro: %s", $1); if (macro->type != MACRO_STRING) yyerror("string macro expected: %s", $1); $$ = xstrdup(macro->value.str); xfree($1); } /** STRV: (char *) */ strv: xstrv /** [$1: xstrv (char *)] */ { $$ = $1; } | strv '+' xstrv /** [$1: strv (char *)] [$3: xstrv (char *)] */ { size_t size; size = strlen($1) + strlen($3) + 1; $$ = xrealloc($1, 1, size); strlcat($$, $3, size); xfree($3); } /** NUMV: (long long) */ numv: NUMCOMMAND { const char *errstr; char *s; s = run_command($1, parse_file->path); $$ = strtonum(s, 0, LLONG_MAX, &errstr); if (errstr != NULL) yyerror("number is %s", errstr); xfree(s); xfree($1); } | NUMBER { $$ = $1; } | NUMMACRO { struct macro *macro; if (strlen($1) > MAXNAMESIZE) yyerror("macro name too long: %s", $1); if ((macro = find_macro($1)) == NULL) yyerror("undefined macro: %s", $1); if (macro->type != MACRO_NUMBER) yyerror("number macro expected: %s", $1); $$ = macro->value.num; xfree($1); } /** REPLSTRV: (char *) */ replstrv: strv /** [$1: strv (char *)] */ { struct replstr rs; rs.str = $1; $$ = replacestr(&rs, parse_tags, NULL, NULL); xfree($1); } /** REPLPATHV: (char *) */ replpathv: strv /** [$1: strv (char *)] */ { struct replpath rp; rp.str = $1; $$ = replacepath(&rp, parse_tags, NULL, NULL, conf.user_home); xfree($1); } /** SIZE: (long long) */ size: numv /** [$1: numv (long long)] */ { $$ = $1; } | numv TOKBYTES /** [$1: numv (long long)] */ { $$ = $1; } | numv TOKKILOBYTES /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / 1024) yyerror("size is too big"); $$ = $1 * 1024; } | numv TOKMEGABYTES /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / (1024 * 1024)) yyerror("size is too big"); $$ = $1 * (1024 * 1024); } | numv TOKGIGABYTES /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / (1024 * 1024 * 1024)) yyerror("size is too big"); $$ = $1 * (1024 * 1024 * 1024); } /** TIME: (long long) */ time: numv /** [$1: numv (long long)] */ { $$ = $1; } | numv TOKHOURS /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_HOUR) yyerror("time is too long"); $$ = $1 * TIME_HOUR; } | numv TOKMINUTES /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_MINUTE) yyerror("time is too long"); $$ = $1 * TIME_MINUTE; } | numv TOKSECONDS /** [$1: numv (long long)] */ { $$ = $1; } | numv TOKDAYS /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_DAY) yyerror("time is too long"); $$ = $1 * TIME_DAY; } | numv TOKWEEKS /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_WEEK) yyerror("time is too long"); $$ = $1 * TIME_WEEK; } | numv TOKMONTHS /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_MONTH) yyerror("time is too long"); $$ = $1 * TIME_MONTH; } | numv TOKYEARS /** [$1: numv (long long)] */ { if ($1 > LLONG_MAX / TIME_YEAR) yyerror("time is too long"); $$ = $1 * TIME_YEAR; } /** EXPIRE: (long long) */ expire: TOKEXPIRE time /** [$2: time (long long)] */ { #if UINT64_MAX < LLONG_MAX if ($2 > UINT64_MAX) yyerror("time too long"); #endif $$ = $2; } | /* empty */ { $$ = -1; } /** CACHE */ cache: TOKCACHE replpathv expire /** [$2: replpathv (char *)] [$3: expire (long long)] */ { struct cache *cache; TAILQ_FOREACH(cache, &conf.caches, entry) { if (strcmp(cache->path, $2) == 0) yyerror("duplicate cache path"); } cache = xcalloc(1, sizeof *cache); cache->path = $2; cache->expire = $3; TAILQ_INSERT_TAIL(&conf.caches, cache, entry); log_debug2("added cache \"%s\": expire %lld", cache->path, $3); } /** SET */ set: TOKSET TOKMAXSIZE size /** [$3: size (long long)] */ { if ($3 == 0) yyerror("zero maximum size"); if ($3 > MAXMAILSIZE) yyerror("maximum size too large: %lld", $3); conf.max_size = $3; } | TOKSET TOKLOCKTYPES locklist /** [$3: locklist (u_int)] */ { if ($3 & LOCK_FCNTL && $3 & LOCK_FLOCK) yyerror("fcntl and flock locking cannot be used together"); conf.lock_types = $3; } | TOKSET TOKLOCKFILE replpathv /** [$3: replpathv (char *)] */ { if (conf.lock_file != NULL) xfree(conf.lock_file); conf.lock_file = $3; } | TOKSET TOKDELTOOBIG { conf.del_big = 1; } | TOKSET TOKALLOWMANY { conf.allow_many = 1; } | TOKSET TOKDEFUSER strv /** [$3: strv (char *)] */ { if (conf.def_user == NULL) conf.def_user = $3; } | TOKSET TOKCMDUSER strv /** [$3: strv (char *)] */ { if (conf.cmd_user == NULL) conf.cmd_user = $3; } | TOKSET TOKSTRIPCHARACTERS strv /** [$3: strv (char *)] */ { xfree(conf.strip_chars); conf.strip_chars = $3; } | TOKSET TOKTIMEOUT time /** [$3: time (long long)] */ { if ($3 == 0) yyerror("zero timeout"); if ($3 > INT_MAX / 1000) yyerror("timeout too long: %lld", $3); conf.timeout = $3 * 1000; } | TOKSET TOKQUEUEHIGH numv /** [$3: numv (long long)] */ { if ($3 == 0) yyerror("zero queue-high"); if ($3 > MAXQUEUEVALUE) yyerror("queue-high too big: %lld", $3); if (conf.queue_low != -1 && $3 <= conf.queue_low) yyerror("queue-high must be larger than queue-low"); conf.queue_high = $3; } | TOKSET TOKQUEUELOW numv /** [$3: numv (long long)] */ { if ($3 > MAXQUEUEVALUE) yyerror("queue-low too big: %lld", $3); if (conf.queue_high == -1) yyerror("queue-high not specified"); if ($3 >= conf.queue_high) yyerror("queue-low must be smaller than queue-high"); conf.queue_low = $3; } | TOKSET TOKPARALLELACCOUNTS numv /** [$3: numv (long long)] */ { if ($3 > INT_MAX) yyerror("parallel-accounts too big: %lld", $3); if ($3 == 0) yyerror("parallel-accounts cannot be zero"); conf.max_accts = $3; } | TOKSET TOKPROXY replstrv /** [$3: replstrv (char *)] */ { if (conf.proxy != NULL) { xfree(conf.proxy->server.host); xfree(conf.proxy->server.port); if (conf.proxy->user != NULL) xfree(conf.proxy->user); if (conf.proxy->pass != NULL) xfree(conf.proxy->pass); } if ((conf.proxy = getproxy($3)) == NULL) yyerror("invalid proxy"); xfree($3); } | TOKSET TOKVERIFYCERTS { conf.verify_certs = 1; } | TOKSET TOKIMPLACT TOKKEEP { conf.impl_act = DECISION_KEEP; } | TOKSET TOKIMPLACT TOKDROP { conf.impl_act = DECISION_DROP; } | TOKSET TOKPURGEAFTER numv /** [$3: numv (long long)] */ { if ($3 == 0) yyerror("invalid purge-after value: 0"); if ($3 > UINT_MAX) yyerror("purge-after value too large: %lld", $3); conf.purge_after = $3; } | TOKSET TOKPURGEAFTER TOKNONE { conf.purge_after = 0; } | TOKSET TOKNORECEIVED { conf.no_received = 1; } | TOKSET TOKNOCREATE { conf.no_create = 1; } | TOKSET TOKFILEGROUP TOKUSER { conf.file_group = -1; } | TOKSET TOKFILEGROUP localgid /** [$3: localgid (gid_t)] */ { conf.file_group = $3; } | TOKSET TOKFILEUMASK TOKUSER { conf.file_umask = umask(0); umask(conf.file_umask); } | TOKSET TOKLOOKUPORDER ufnlist /** [$3: ufnlist (struct userfunctions *)] */ { ARRAY_FREEALL(conf.user_order); conf.user_order = $3; } | TOKSET TOKFILEUMASK numv /** [$3: numv (long long)] */ { char s[8]; u_int n; /* * We can't differentiate umasks in octal from normal numbers * (requiring a leading zero a la C would be nice, but it would * potentially break existing configs), so we need to fiddle to * convert. */ memset(s, 0, sizeof s); xsnprintf(s, sizeof s, "%03lld", $3); if (s[3] != '\0' || s[0] < '0' || s[0] > '7' || s[1] < 0 || s[1] > '7' || s[2] < '0' || s[2] > '7') yyerror("invalid umask: %s", s); if (sscanf(s, "%o", &n) != 1) yyerror("invalid umask: %s", s); conf.file_umask = n; } /** DEFMACRO */ defmacro: STRMACRO '=' strv /** [$3: strv (char *)] */ { struct macro *macro; if (strlen($1) > MAXNAMESIZE) yyerror("macro name too long: %s", $1); macro = xmalloc(sizeof *macro); strlcpy(macro->name, $1, sizeof macro->name); macro->type = MACRO_STRING; macro->value.str = $3; if (parse_last == NULL) TAILQ_INSERT_HEAD(&parse_macros, macro, entry); else { TAILQ_INSERT_AFTER( &parse_macros, parse_last, macro, entry); } log_debug3("added macro \"%s\": \"%s\"", macro->name, macro->value.str); xfree($1); } | NUMMACRO '=' numv /** [$3: numv (long long)] */ { struct macro *macro; if (strlen($1) > MAXNAMESIZE) yyerror("macro name too long: %s", $1); macro = xmalloc(sizeof *macro); strlcpy(macro->name, $1, sizeof macro->name); macro->type = MACRO_NUMBER; macro->value.num = $3; if (parse_last == NULL) TAILQ_INSERT_HEAD(&parse_macros, macro, entry); else { TAILQ_INSERT_AFTER( &parse_macros, parse_last, macro, entry); } log_debug3("added macro \"%s\": %lld", macro->name, macro->value.num); xfree($1); } /** REPLSTRSLIST: (struct replstrs *) */ replstrslist: replstrslist strv /** [$1: replstrslist (struct replstrs *)] [$2: strv (char *)] */ { if (*$2 == '\0') yyerror("empty string in list"); $$ = $1; ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $2; } | strv /** [$1: strv (char *)] */ { if (*$1 == '\0') yyerror("empty string in list"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $1; } /** STRINGSLIST: (struct strings *) */ stringslist: stringslist replstrv /** [$1: stringslist (struct strings *)] [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("empty string in list"); $$ = $1; ARRAY_ADD($$, $2); } | replstrv /** [$1: replstrv (char *)] */ { if (*$1 == '\0') yyerror("empty string in list"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $1); } /** PATHSLIST: (struct strings *) */ pathslist: pathslist replpathv /** [$1: pathslist (struct strings *)] [$2: replpathv (char *)] */ { if (*$2 == '\0') yyerror("invalid path"); $$ = $1; ARRAY_ADD($$, $2); } | replpathv /** [$1: replpathv (char *)] */ { if (*$1 == '\0') yyerror("invalid path"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $1); } /** UFN: (userfunction) */ ufn: TOKPASSWD { $$ = &passwd_lookup; } | TOKCOURIER { #ifdef LOOKUP_COURIER $$ = &courier_lookup; #else yyerror("support for lookup-order courier is not enabled"); #endif } /** UFNLIST: (struct userfunctions *) */ ufnlist: ufnlist ufn /** [$1: ufnlist (struct userfunctions *)] [$2: ufn (userfunction)] */ { $$ = $1; ARRAY_ADD($$, $2); } | ufn /** [$1: ufn (userfunction)] */ { $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $1); } /** RMHEADERS: (struct replstrs *) */ rmheaders: rmheaderp strv /** [$2: strv (char *)] */ { if (*$2 == '\0') yyerror("invalid header"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $2; } | rmheaderp '{' replstrslist '}' /** [$3: replstrslist (struct replstrs *)] */ { $$ = $3; } /** MAILDIRS: (struct strings *) */ maildirs: maildirp replpathv /** [$2: replpathv (char *)] */ { if (*$2 == '\0') yyerror("invalid path"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $2); } | maildirp '{' pathslist '}' /** [$3: pathslist (struct strings *)] */ { $$ = $3; } /** MBOXES: (struct strings *) */ mboxes: mboxp replpathv /** [$2: replpathv (char *)] */ { if (*$2 == '\0') yyerror("invalid path"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $2); } | mboxp '{' pathslist '}' /** [$3: pathslist (struct strings *)] */ { $$ = $3; } /** FOLDERS: (struct strings *) */ folders: folderp replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid folder"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $2); } | folderp '{' stringslist '}' /** [$3: stringslist (struct strings *)] */ { $$ = $3; } /** LOCK: (u_int) */ lock: TOKFCNTL { $$ = LOCK_FCNTL; } | TOKFLOCK { $$ = LOCK_FLOCK; } | TOKDOTLOCK { $$ = LOCK_DOTLOCK; } /** LOCKLIST: (u_int) */ locklist: locklist lock /** [$1: locklist (u_int)] [$2: lock (u_int)] */ { $$ = $1 | $2; } | lock /** [$1: lock (u_int)] */ { $$ = $1; } | TOKNONE { $$ = 0; } /** LOCALGID: (gid_t) */ localgid: replstrv /** [$1: replstrv (char *)] */ { struct group *gr; if (*$1 == '\0') yyerror("invalid group"); gr = getgrnam($1); if (gr == NULL) yyerror("unknown group: %s", $1); $$ = gr->gr_gid; endgrent(); xfree($1); } | numv /** [$1: numv (long long)] */ { struct group *gr; if ($1 > GID_MAX) yyerror("invalid gid: %llu", $1); gr = getgrgid($1); if (gr == NULL) yyerror("unknown gid: %llu", $1); $$ = gr->gr_gid; endgrent(); } /** USER: (char *) */ user: /* empty */ { $$ = NULL; } | TOKUSER strv /** [$2: strv (char *)] */ { $$ = $2; } /** USERS: (struct replstrs *) */ users: /* empty */ { $$ = NULL; } | userp strv /** [$2: strv (char *)] */ { $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $2; } | userp '{' replstrslist '}' /** [$3: replstrslist (struct replstrs *)] */ { $$ = $3; } /** CASERE: (struct { ... } re) */ casere: TOKCASE replstrv /** [$2: replstrv (char *)] */ { /* match case */ $$.flags = 0; $$.str = $2; } | replstrv /** [$1: replstrv (char *)] */ { /* ignore case */ $$.flags = RE_IGNCASE; $$.str = $1; } /** NOT: (int) */ not: TOKNOT { $$ = 1; } | /* empty */ { $$ = 0; } /** KEEP: (int) */ keep: TOKKEEP { $$ = 1; } | /* empty */ { $$ = 0; } /** DISABLED: (int) */ disabled: TOKDISABLED { $$ = 1; } | /* empty */ { $$ = 0; } /** PORT: (char *) */ port: TOKPORT replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid port"); $$ = $2; } | TOKPORT numv /** [$2: numv (long long)] */ { if ($2 == 0 || $2 > 65535) yyerror("invalid port"); xasprintf(&$$, "%lld", $2); } /** SERVER: (struct { ... } server) */ server: TOKSERVER replstrv port /** [$2: replstrv (char *)] [$3: port (char *)] */ { if (*$2 == '\0') yyerror("invalid host"); $$.host = $2; $$.port = $3; } | TOKSERVER replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid host"); $$.host = $2; $$.port = NULL; } /** TO: (char *) */ to: /* empty */ { $$ = NULL; } | TOKTO strv /** [$2: strv (char *)] */ { $$ = $2; } /** FROM: (char *) */ from: /* empty */ { $$ = NULL; } | TOKFROM strv /** [$2: strv (char *)] */ { $$ = $2; } /** COMPRESS: (int) */ compress: TOKCOMPRESS { $$ = 1; } | /* empty */ { $$ = 0; } /** ACTITEM: (struct actitem *) */ actitem: execpipe strv /** [$1: execpipe (int)] [$2: strv (char *)] */ { struct deliver_pipe_data *data; if (*$2 == '\0') yyerror("invalid command"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_pipe; data = xcalloc(1, sizeof *data); $$->data = data; data->pipe = $1; data->cmd.str = $2; } | TOKREWRITE strv /** [$2: strv (char *)] */ { struct deliver_rewrite_data *data; if (*$2 == '\0') yyerror("invalid command"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_rewrite; data = xcalloc(1, sizeof *data); $$->data = data; data->cmd.str = $2; } | writeappend strv /** [$1: writeappend (int)] [$2: strv (char *)] */ { struct deliver_write_data *data; if (*$2 == '\0') yyerror("invalid path"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_write; data = xcalloc(1, sizeof *data); $$->data = data; data->append = $1; data->path.str = $2; } | TOKMAILDIR strv /** [$2: strv (char *)] */ { struct deliver_maildir_data *data; if (*$2 == '\0') yyerror("invalid path"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_maildir; data = xcalloc(1, sizeof *data); $$->data = data; data->path.str = $2; } | rmheaders /** [$1: rmheaders (struct replstrs *)] */ { struct deliver_remove_header_data *data; $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_remove_header; data = xcalloc(1, sizeof *data); $$->data = data; data->hdrs = $1; } | TOKADDHEADER strv val /** [$2: strv (char *)] [$3: val (char *)] */ { struct deliver_add_header_data *data; if (*$2 == '\0') yyerror("invalid header"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_add_header; data = xcalloc(1, sizeof *data); $$->data = data; data->hdr.str = $2; data->value.str = $3; } | TOKMBOX strv compress /** [$2: strv (char *)] [$3: compress (int)] */ { struct deliver_mbox_data *data; if (*$2 == '\0') yyerror("invalid path"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_mbox; data = xcalloc(1, sizeof *data); $$->data = data; data->path.str = $2; data->compress = $3; } | imaptype server userpassnetrc folder1 verify nocrammd5 nologin tls1 /** [$1: imaptype (int)] [$2: server (struct { ... } server)] */ /** [$3: userpassnetrc (struct { ... } userpass)] [$4: folder1 (char *)] */ /** [$5: verify (int)] [$6: nocrammd5 (int)] [$7: nologin (int)] */ /** [$8: tls1 (int)] */ { struct deliver_imap_data *data; $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_imap; data = xcalloc(1, sizeof *data); $$->data = data; if ($3.user_netrc && $3.pass_netrc) find_netrc($2.host, &data->user, &data->pass); else { if ($3.user_netrc) find_netrc($2.host, &data->user, NULL); else data->user = $3.user; if ($3.pass_netrc) find_netrc($2.host, NULL, &data->pass); else data->pass = $3.pass; } data->folder.str = $4; data->server.ssl = $1; data->server.verify = $5; data->server.tls1 = $8; data->server.host = $2.host; if ($2.port != NULL) data->server.port = $2.port; else if ($1) data->server.port = xstrdup("imaps"); else data->server.port = xstrdup("imap"); data->server.ai = NULL; data->nocrammd5 = $6; data->nologin = $7; } | TOKSMTP server from to /** [$2: server (struct { ... } server)] [$3: from (char *)] */ /** [$4: to (char *)] */ { struct deliver_smtp_data *data; $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_smtp; data = xcalloc(1, sizeof *data); $$->data = data; data->server.host = $2.host; if ($2.port != NULL) data->server.port = $2.port; else data->server.port = xstrdup("smtp"); data->server.ai = NULL; data->from.str = $3; data->to.str = $4; } | TOKSTDOUT { $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_stdout; } | TOKTAG strv optval /** [$2: strv (char *)] [$3: optval (char *)] */ { struct deliver_tag_data *data; if (*$2 == '\0') yyerror("invalid tag"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_tag; data = xcalloc(1, sizeof *data); $$->data = data; data->key.str = $2; data->value.str = $3; } | TOKADDTOCACHE replpathv TOKKEY strv /** [$2: replpathv (char *)] [$4: strv (char *)] */ { struct deliver_add_to_cache_data *data; if (*$2 == '\0') yyerror("invalid path"); if (*$4 == '\0') yyerror("invalid key"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_add_to_cache; data = xcalloc(1, sizeof *data); $$->data = data; data->key.str = $4; data->path = $2; } | TOKREMOVEFROMCACHE replpathv TOKKEY strv /** [$2: replpathv (char *)] [$4: strv (char *)] */ { struct deliver_remove_from_cache_data *data; if (*$2 == '\0') yyerror("invalid path"); if (*$4 == '\0') yyerror("invalid key"); $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_remove_from_cache; data = xcalloc(1, sizeof *data); $$->data = data; data->key.str = $4; data->path = $2; } | actions /** [$1: actions (struct replstrs *)] */ { struct deliver_action_data *data; /* * This is a special-case, handled when the list of delivery * targets is resolved rather than by calling a deliver * function, so the deliver pointer is NULL. */ $$ = xcalloc(1, sizeof *$$); $$->deliver = NULL; data = xcalloc(1, sizeof *data); $$->data = data; data->actions = $1; } | TOKDROP { $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_drop; } | TOKKEEP { $$ = xcalloc(1, sizeof *$$); $$->deliver = &deliver_keep; } /** ACTLIST: (struct actlist *) */ actlist: actlist actitem /** [$1: actlist (struct actlist *)] [$2: actitem (struct actitem *)] */ { $$ = $1; TAILQ_INSERT_TAIL($$, $2, entry); $2->idx = parse_actionidx++; } | actitem /** [$1: actitem (struct actitem *)] */ { $$ = xmalloc(sizeof *$$); TAILQ_INIT($$); TAILQ_INSERT_HEAD($$, $1, entry); $1->idx = 0; parse_actionidx = 1; } /** DEFACTION */ defaction: TOKACTION replstrv users actitem /** [$2: replstrv (char *)] [$3: users (struct replstrs *)] */ /** [$4: actitem (struct actitem *)] */ { struct action *t; if (strlen($2) >= MAXNAMESIZE) yyerror("action name too long: %s", $2); if (*$2 == '\0') yyerror("invalid action name"); if (find_action($2) != NULL) yyerror("duplicate action: %s", $2); t = xmalloc(sizeof *t); strlcpy(t->name, $2, sizeof t->name); t->list = xmalloc(sizeof *t->list); TAILQ_INIT(t->list); TAILQ_INSERT_HEAD(t->list, $4, entry); $4->idx = 0; t->users = $3; TAILQ_INSERT_TAIL(&conf.actions, t, entry); print_action(t); xfree($2); } | TOKACTION replstrv users '{' actlist '}' /** [$2: replstrv (char *)] [$3: users (struct replstrs *)] */ /** [$5: actlist (struct actlist *)] */ { struct action *t; if (strlen($2) >= MAXNAMESIZE) yyerror("action name too long: %s", $2); if (*$2 == '\0') yyerror("invalid action name"); if (find_action($2) != NULL) yyerror("duplicate action: %s", $2); t = xmalloc(sizeof *t); strlcpy(t->name, $2, sizeof t->name); t->list = $5; t->users = $3; TAILQ_INSERT_TAIL(&conf.actions, t, entry); print_action(t); xfree($2); } /** ACCOUNTS: (struct replstrs *) */ accounts: accountp strv /** [$2: strv (char *)] */ { if (*$2 == '\0') yyerror("invalid account name"); if (!have_accounts($2)) yyerror("no matching accounts: %s", $2); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $2; } | accountp '{' replstrslist '}' /** [$3: replstrslist (struct replstrs *)] */ { $$ = $3; } /** ACTIONS: (struct replstrs *) */ actions: actionp strv /** [$2: strv (char *)] */ { if (*$2 == '\0') yyerror("invalid action name"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_EXPAND($$, 1); ARRAY_LAST($$).str = $2; } | actionp '{' replstrslist '}' /** [$3: replstrslist (struct replstrs *)] */ { $$ = $3; } /** CONT: (int) */ cont: /* empty */ { $$ = 0; } | TOKCONTINUE { $$ = 1; } /** AREA: (enum area) */ area: /* empty */ { $$ = AREA_ANY; } | TOKIN TOKALL { $$ = AREA_ANY; } | TOKIN TOKHEADERS { $$ = AREA_HEADERS; } | TOKIN TOKBODY { $$ = AREA_BODY; } /** RETRC: (long long) */ retrc: numv /** [$1: numv (long long)] */ { if ($1 < 0 || $1 > 255) yyerror("invalid return code"); $$ = $1; } | /* empty */ { $$ = -1; } /** RETRE: (struct { ... } re) */ retre: casere /** [$1: casere (struct { ... } re)] */ { $$ = $1; } | /* empty */ { $$.str = NULL; } /** LTGT: (enum cmp) */ ltgt: '<' { $$ = CMP_LT; } | '>' { $$ = CMP_GT; } /** EQNE: (enum cmp) */ eqne: TOKEQ { $$ = CMP_EQ; } | TOKNE { $$ = CMP_NE; } /** CMP: (enum cmp) */ cmp: ltgt /** [$1: ltgt (enum cmp)] */ { $$ = $1; } | eqne /** [$1: eqne (enum cmp)] */ { $$ = $1; } /** EXECPIPE: (int) */ execpipe: TOKEXEC { $$ = 0; } | TOKPIPE { $$ = 1; } /** WRITEAPPEND: (int) */ writeappend: TOKWRITE { $$ = 0; } | TOKAPPEND { $$ = 1; } /** EXPROP: (enum exprop) */ exprop: TOKAND { $$ = OP_AND; } | TOKOR { $$ = OP_OR; } /** EXPRITEM: (struct expritem *) */ expritem: not TOKALL /** [$1: not (int)] */ { $$ = xcalloc(1, sizeof *$$); $$->match = &match_all; $$->inverted = $1; } | not casere area /** [$1: not (int)] [$2: casere (struct { ... } re)] */ /** [$3: area (enum area)] */ { struct match_regexp_data *data; char *cause; $$ = xcalloc(1, sizeof *$$); $$->match = &match_regexp; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->area = $3; if (re_compile(&data->re, $2.str, $2.flags, &cause) != 0) yyerror("%s", cause); xfree($2.str); } | not accounts /** [$1: not (int)] [$2: accounts (struct replstrs *)] */ { struct match_account_data *data; $$ = xcalloc(1, sizeof *$$); $$->match = &match_account; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->accounts = $2; } | not execpipe strv user TOKRETURNS '(' retrc ',' retre ')' /** [$1: not (int)] [$2: execpipe (int)] [$3: strv (char *)] */ /** [$4: user (char *)] [$7: retrc (long long)] */ /** [$9: retre (struct { ... } re)] */ { struct match_command_data *data; char *cause; if (*$3 == '\0' || ($3[0] == '|' && $3[1] == '\0')) yyerror("invalid command"); if ($7 == -1 && $9.str == NULL) yyerror("return code or regexp must be specified"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_command; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->user.str = $4; data->pipe = $2; data->cmd.str = $3; data->ret = $7; if ($9.str != NULL) { if (re_compile( &data->re, $9.str, $9.flags, &cause) != 0) yyerror("%s", cause); xfree($9.str); } } | not TOKTAGGED strv /** [$1: not (int)] [$3: strv (char *)] */ { struct match_tagged_data *data; if (*$3 == '\0') yyerror("invalid tag"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_tagged; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->tag.str = $3; } | not TOKSIZE ltgt size /** [$1: not (int)] [$3: ltgt (enum cmp)] [$4: size (long long)] */ { struct match_size_data *data; #if SIZE_MAX < LLONG_MAX if ($4 > SIZE_MAX) yyerror("size too large"); #endif $$ = xcalloc(1, sizeof *$$); $$->match = &match_size; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->size = $4; data->cmp = $3; } | not TOKSTRING strv TOKTO casere /** [$1: not (int)] [$3: strv (char *)] */ /** [$5: casere (struct { ... } re)] */ { struct match_string_data *data; char *cause; if (*$3 == '\0') yyerror("invalid string"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_string; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->str.str = $3; if (re_compile( &data->re, $5.str, $5.flags|RE_NOSUBST, &cause) != 0) yyerror("%s", cause); xfree($5.str); } | not TOKINCACHE replpathv TOKKEY strv /** [$1: not (int)] [$3: replpathv (char *)] [$5: strv (char *)] */ { struct match_in_cache_data *data; if (*$3 == '\0') yyerror("invalid path"); if (*$5 == '\0') yyerror("invalid key"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_in_cache; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->key.str = $5; data->path = $3; } | not TOKMATCHED /** [$1: not (int)] */ { $$ = xcalloc(1, sizeof *$$); $$->match = &match_matched; $$->inverted = $1; } | not TOKUNMATCHED /** [$1: not (int)] */ { $$ = xcalloc(1, sizeof *$$); $$->match = &match_unmatched; $$->inverted = $1; } | not TOKAGE ltgt time /** [$1: not (int)] [$3: ltgt (enum cmp)] [$4: time (long long)] */ { struct match_age_data *data; if ($4 == 0) yyerror("invalid time"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_age; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->time = $4; data->cmp = $3; } | not TOKAGE TOKINVALID /** [$1: not (int)] */ { struct match_age_data *data; $$ = xcalloc(1, sizeof *$$); $$->match = &match_age; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->time = -1; } | not TOKATTACHMENT TOKCOUNT cmp numv /** [$1: not (int)] [$4: cmp (enum cmp)] [$5: numv (long long)] */ { struct match_attachment_data *data; $$ = xcalloc(1, sizeof *$$); $$->match = &match_attachment; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->op = ATTACHOP_COUNT; data->cmp = $4; data->value.num = $5; } | not TOKATTACHMENT TOKTOTALSIZE ltgt size /** [$1: not (int)] [$4: ltgt (enum cmp)] [$5: size (long long)] */ { struct match_attachment_data *data; #if SIZE_MAX < LLONG_MAX if ($5 > SIZE_MAX) yyerror("size too large"); #endif $$ = xcalloc(1, sizeof *$$); $$->match = &match_attachment; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->op = ATTACHOP_TOTALSIZE; data->cmp = $4; data->value.size = $5; } | not TOKATTACHMENT TOKANYSIZE ltgt size /** [$1: not (int)] [$4: ltgt (enum cmp)] [$5: size (long long)] */ { struct match_attachment_data *data; #if SIZE_MAX < LLONG_MAX if ($5 > SIZE_MAX) yyerror("size too large"); #endif $$ = xcalloc(1, sizeof *$$); $$->match = &match_attachment; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->op = ATTACHOP_ANYSIZE; data->cmp = $4; data->value.size = $5; } | not TOKATTACHMENT TOKANYTYPE strv /** [$1: not (int)] [$4: strv (char *)] */ { struct match_attachment_data *data; if (*$4 == '\0') yyerror("invalid string"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_attachment; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->op = ATTACHOP_ANYTYPE; data->value.str.str = $4; } | not TOKATTACHMENT TOKANYNAME strv /** [$1: not (int)] [$4: strv (char *)] */ { struct match_attachment_data *data; if (*$4 == '\0') yyerror("invalid string"); $$ = xcalloc(1, sizeof *$$); $$->match = &match_attachment; $$->inverted = $1; data = xcalloc(1, sizeof *data); $$->data = data; data->op = ATTACHOP_ANYNAME; data->value.str.str = $4; } /** EXPRLIST: (struct expr *) */ exprlist: exprlist exprop expritem /** [$1: exprlist (struct expr *)] [$2: exprop (enum exprop)] */ /** [$3: expritem (struct expritem *)] */ { $$ = $1; $3->op = $2; TAILQ_INSERT_TAIL($$, $3, entry); } | exprop expritem /** [$1: exprop (enum exprop)] [$2: expritem (struct expritem *)] */ { $$ = xmalloc(sizeof *$$); TAILQ_INIT($$); $2->op = $1; TAILQ_INSERT_HEAD($$, $2, entry); } /** EXPR: (struct expr *) */ expr: expritem /** [$1: expritem (struct expritem *)] */ { $$ = xmalloc(sizeof *$$); TAILQ_INIT($$); TAILQ_INSERT_HEAD($$, $1, entry); } | expritem exprlist /** [$1: expritem (struct expritem *)] [$2: exprlist (struct expr *)] */ { $$ = $2; TAILQ_INSERT_HEAD($$, $1, entry); } /** PERFORM: (struct rule *) */ perform: users actionp actitem cont /** [$1: users (struct replstrs *)] [$3: actitem (struct actitem *)] */ /** [$4: cont (int)] */ { struct action *t; $$ = xcalloc(1, sizeof *$$); $$->idx = parse_ruleidx++; $$->actions = NULL; TAILQ_INIT(&$$->rules); $$->stop = !$4; $$->users = $1; t = $$->lambda = xcalloc(1, sizeof *$$->lambda); xsnprintf(t->name, sizeof t->name, "", $$->idx); t->users = NULL; t->list = xmalloc(sizeof *t->list); TAILQ_INIT(t->list); TAILQ_INSERT_HEAD(t->list, $3, entry); $3->idx = 0; if (parse_rule == NULL) TAILQ_INSERT_TAIL(&conf.rules, $$, entry); else TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry); } | users actionp '{' actlist '}' cont /** [$1: users (struct replstrs *)] */ /** [$4: actlist (struct actlist *)] [$6: cont (int)] */ { struct action *t; $$ = xcalloc(1, sizeof *$$); $$->idx = parse_ruleidx++; $$->actions = NULL; TAILQ_INIT(&$$->rules); $$->stop = !$6; $$->users = $1; t = $$->lambda = xcalloc(1, sizeof *$$->lambda); xsnprintf(t->name, sizeof t->name, "", $$->idx); t->users = NULL; t->list = $4; if (parse_rule == NULL) TAILQ_INSERT_TAIL(&conf.rules, $$, entry); else TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry); } | users actions cont /** [$1: users (struct replstrs *)] */ /** [$2: actions (struct replstrs *)] [$3: cont (int)] */ { $$ = xcalloc(1, sizeof *$$); $$->idx = parse_ruleidx++; $$->lambda = NULL; $$->actions = $2; TAILQ_INIT(&$$->rules); $$->stop = !$3; $$->users = $1; if (parse_rule == NULL) TAILQ_INSERT_TAIL(&conf.rules, $$, entry); else TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry); } | '{' { $$ = xcalloc(1, sizeof *$$); $$->idx = parse_ruleidx++; $$->lambda = NULL; $$->actions = NULL; TAILQ_INIT(&$$->rules); $$->stop = 0; $$->users = NULL; if (parse_rule == NULL) TAILQ_INSERT_TAIL(&conf.rules, $$, entry); else TAILQ_INSERT_TAIL(&parse_rule->rules, $$, entry); ARRAY_ADD(&parse_rulestack, parse_rule); parse_rule = $$; } /** CLOSE */ close: '}' { if (parse_rule == NULL) yyerror("missing {"); parse_rule = ARRAY_LAST(&parse_rulestack); ARRAY_TRUNC(&parse_rulestack, 1); } /** RULE */ rule: TOKMATCH expr perform /** [$2: expr (struct expr *)] [$3: perform (struct rule *)] */ { $3->expr = $2; print_rule($3); } /** FOLDERLIST: (struct strings *) */ folderlist: /* empty */ { $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, xstrdup("INBOX")); } | folders /** [$1: folders (struct strings *)] */ { $$ = $1; } /** FOLDER1: (char *) */ folder1: /* empty */ { $$ = xstrdup("INBOX"); } | folderp strv /** [$2: strv (char *)] */ { $$ = $2; } /** GROUPS: (struct strings *) */ groups: groupp replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid group"); $$ = xmalloc(sizeof *$$); ARRAY_INIT($$); ARRAY_ADD($$, $2); } | groupp '{' stringslist '}' /** [$3: stringslist (struct strings *)] */ { $$ = $3; } /** NOCRAMMD5: (int) */ nocrammd5: TOKNOCRAMMD5 { $$ = 1; } | /* empty */ { $$ = 0; } /** NOLOGIN: (int) */ nologin: TOKNOLOGIN { $$ = 1; } | /* empty */ { $$ = 0; } /** TLS1: (int) */ tls1: TOKNOTLS1 { $$ = 0; } | /* empty */ { $$ = 1; } /** UIDL: (int) */ uidl: TOKNOUIDL { $$ = 0; } | /* empty */ { $$ = 1; } /** VERIFY: (int) */ verify: TOKNOVERIFY { $$ = 0; } | /* empty */ { $$ = 1; } /** APOP: (int) */ apop: TOKNOAPOP { $$ = 0; } | /* empty */ { $$ = 1; } /** ONLY: (enum fetch_only) */ only: TOKNEWONLY { $$ = FETCH_ONLY_NEW; } | TOKOLDONLY { $$ = FETCH_ONLY_OLD; } /** POPTYPE: (int) */ poptype: TOKPOP3 { $$ = 0; } | TOKPOP3S { $$ = 1; } /** IMAPTYPE: (int) */ imaptype: TOKIMAP { $$ = 0; } | TOKIMAPS { $$ = 1; } /** NNTPTYPE: (int) */ nntptype: TOKNNTP { $$ = 0; } | TOKNNTPS { $$ = 1; } /** USERPASSNETRC: (struct { ... } userpass) */ userpassnetrc: TOKUSER replstrv TOKPASS replstrv /** [$2: replstrv (char *)] [$4: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid user"); if (*$4 == '\0') yyerror("invalid pass"); $$.user = $2; $$.user_netrc = 0; $$.pass = $4; $$.pass_netrc = 0; } | /* empty */ { $$.user = NULL; $$.user_netrc = 1; $$.pass = NULL; $$.pass_netrc = 1; } | TOKUSER replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid user"); $$.user = $2; $$.user_netrc = 0; $$.pass = NULL; $$.pass_netrc = 1; } | TOKPASS replstrv /** [$2: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid pass"); $$.user = NULL; $$.user_netrc = 1; $$.pass = $2; $$.pass_netrc = 0; } /** USERPASSREQD: (struct { ... } userpass) */ userpassreqd: TOKUSER replstrv TOKPASS replstrv /** [$2: replstrv (char *)] [$4: replstrv (char *)] */ { if (*$2 == '\0') yyerror("invalid user"); if (*$4 == '\0') yyerror("invalid pass"); $$.user = $2; $$.user_netrc = 0; $$.pass = $4; $$.pass_netrc = 0; } /** USERPASS: (struct { ... } userpass) */ userpass: userpassreqd /** [$1: userpassreqd (struct { ... } userpass)] */ { $$.user = $1.user; $$.user_netrc = $1.user_netrc; $$.pass = $1.pass; $$.pass_netrc = $1.pass_netrc; } | /* empty */ { $$.user = NULL; $$.user_netrc = 0; $$.pass = NULL; $$.pass_netrc = 0; } /** POPONLY: (struct { ... } poponly) */ poponly: only TOKCACHE replpathv /** [$1: only (enum fetch_only)] [$3: replpathv (char *)] */ { $$.path = $3; $$.only = $1; } | /* empty */ { $$.path = NULL; $$.only = FETCH_ONLY_ALL; } /** IMAPONLY: (enum fetch_only) */ imaponly: only /** [$1: only (enum fetch_only)] */ { $$ = $1; } | /* empty */ { $$ = FETCH_ONLY_ALL; } /** FETCHTYPE: (struct { ... } fetch) */ fetchtype: poptype server userpassnetrc poponly apop verify uidl tls1 /** [$1: poptype (int)] [$2: server (struct { ... } server)] */ /** [$3: userpassnetrc (struct { ... } userpass)] */ /** [$4: poponly (struct { ... } poponly)] [$5: apop (int)] [$6: verify (int)] */ /** [$7: uidl (int)] [$8: tls1 (int)] */ { struct fetch_pop3_data *data; $$.fetch = &fetch_pop3; data = xcalloc(1, sizeof *data); $$.data = data; if ($3.user_netrc && $3.pass_netrc) find_netrc($2.host, &data->user, &data->pass); else { if ($3.user_netrc) find_netrc($2.host, &data->user, NULL); else data->user = $3.user; if ($3.pass_netrc) find_netrc($2.host, NULL, &data->pass); else data->pass = $3.pass; } data->server.ssl = $1; data->server.verify = $6; data->server.tls1 = $8; data->server.host = $2.host; if ($2.port != NULL) data->server.port = $2.port; else if ($1) data->server.port = xstrdup("pop3s"); else data->server.port = xstrdup("pop3"); data->server.ai = NULL; data->apop = $5; data->uidl = $7; data->path = $4.path; data->only = $4.only; } | TOKPOP3 TOKPIPE replstrv userpassreqd poponly apop /** [$3: replstrv (char *)] */ /** [$4: userpassreqd (struct { ... } userpass)] */ /** [$5: poponly (struct { ... } poponly)] [$6: apop (int)] */ { struct fetch_pop3_data *data; $$.fetch = &fetch_pop3pipe; data = xcalloc(1, sizeof *data); $$.data = data; data->user = $4.user; data->pass = $4.pass; data->pipecmd = $3; if (data->pipecmd == NULL || *data->pipecmd == '\0') yyerror("invalid pipe command"); data->apop = $6; data->path = $5.path; data->only = $5.only; } | imaptype server userpassnetrc folderlist imaponly verify nocrammd5 /** [$1: imaptype (int)] [$2: server (struct { ... } server)] */ /** [$3: userpassnetrc (struct { ... } userpass)] */ /** [$4: folderlist (struct strings *)] [$5: imaponly (enum fetch_only)] */ /** [$6: verify (int)] [$7: nocrammd5 (int)] */ nologin tls1 /** [$8: nologin (int)] [$9: tls1 (int)] */ { struct fetch_imap_data *data; $$.fetch = &fetch_imap; data = xcalloc(1, sizeof *data); $$.data = data; if ($3.user_netrc && $3.pass_netrc) find_netrc($2.host, &data->user, &data->pass); else { if ($3.user_netrc) find_netrc($2.host, &data->user, NULL); else data->user = $3.user; if ($3.pass_netrc) find_netrc($2.host, NULL, &data->pass); else data->pass = $3.pass; } data->folders = $4; data->server.ssl = $1; data->server.verify = $6; data->server.tls1 = $9; data->server.host = $2.host; if ($2.port != NULL) data->server.port = $2.port; else if ($1) data->server.port = xstrdup("imaps"); else data->server.port = xstrdup("imap"); data->server.ai = NULL; data->only = $5; data->nocrammd5 = $7; data->nologin = $8; } | TOKIMAP TOKPIPE replstrv userpass folderlist imaponly /** [$3: replstrv (char *)] */ /** [$4: userpass (struct { ... } userpass)] */ /** [$5: folderlist (struct strings *)] [$6: imaponly (enum fetch_only)] */ { struct fetch_imap_data *data; $$.fetch = &fetch_imappipe; data = xcalloc(1, sizeof *data); $$.data = data; data->user = $4.user; data->pass = $4.pass; data->folders = $5; data->pipecmd = $3; if (data->pipecmd == NULL || *data->pipecmd == '\0') yyerror("invalid pipe command"); data->only = $6; } | TOKSTDIN { $$.fetch = &fetch_stdin; } | maildirs /** [$1: maildirs (struct strings *)] */ { struct fetch_maildir_data *data; $$.fetch = &fetch_maildir; data = xcalloc(1, sizeof *data); $$.data = data; data->maildirs = $1; } | mboxes /** [$1: mboxes (struct strings *)] */ { struct fetch_mbox_data *data; $$.fetch = &fetch_mbox; data = xcalloc(1, sizeof *data); $$.data = data; data->mboxes = $1; } | nntptype server userpassnetrc groups TOKCACHE replpathv verify tls1 /** [$1: nntptype (int)] [$2: server (struct { ... } server)] */ /** [$3: userpassnetrc (struct { ... } userpass)] */ /** [$4: groups (struct strings *)] [$6: replpathv (char *)] */ /** [$7: verify (int)] [$8: tls1 (int)] */ { struct fetch_nntp_data *data; char *cause; if (*$6 == '\0') yyerror("invalid cache"); $$.fetch = &fetch_nntp; data = xcalloc(1, sizeof *data); $$.data = data; if ($3.user_netrc && $3.pass_netrc) { if (find_netrc1($2.host, &data->user, &data->pass, &cause) != 0) { log_debug2("%s", cause); xfree(cause); data->user = NULL; data->pass = NULL; } } else { if ($3.user_netrc) find_netrc($2.host, &data->user, NULL); else data->user = $3.user; if ($3.pass_netrc) find_netrc($2.host, NULL, &data->pass); else data->pass = $3.pass; } data->names = $4; data->path = $6; if (data->path == NULL || *data->path == '\0') yyerror("invalid cache"); data->server.ssl = $1; data->server.verify = $7; data->server.tls1 = $8; data->server.host = $2.host; if ($2.port != NULL) data->server.port = $2.port; else if ($1) data->server.port = xstrdup("nntps"); else data->server.port = xstrdup("nntp"); data->server.ai = NULL; } /** ACCOUNT */ account: TOKACCOUNT replstrv disabled users fetchtype keep /** [$2: replstrv (char *)] [$3: disabled (int)] */ /** [$4: users (struct replstrs *)] [$5: fetchtype (struct { ... } fetch)] */ /** [$6: keep (int)] */ { struct account *a; char *su, desc[DESCBUFSIZE]; if (strlen($2) >= MAXNAMESIZE) yyerror("account name too long: %s", $2); if (*$2 == '\0') yyerror("invalid account name"); if (find_account($2) != NULL) yyerror("duplicate account: %s", $2); a = xcalloc(1, sizeof *a); strlcpy(a->name, $2, sizeof a->name); a->keep = $6; a->disabled = $3; a->users = $4; a->fetch = $5.fetch; a->data = $5.data; TAILQ_INSERT_TAIL(&conf.accounts, a, entry); if (a->users != NULL) su = fmt_replstrs(" users=", a->users); else su = xstrdup(""); a->fetch->desc(a, desc, sizeof desc); log_debug2("added account \"%s\":%s fetch=%s", a->name, su, desc); xfree(su); xfree($2); } %% /* Programs */ fdm-1.7+cvs20140912/lex.c0000600000175000017500000003516212142176241013142 0ustar hawkhawk/* $Id: lex.c,v 1.38 2013/05/07 13:07:45 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" #include "y.tab.h" int lex_include; u_int lex_ifdef; int lex_skip; int yylex(void); int cmp_token(const void *, const void *); int read_token(int); long long read_number(int); char *read_macro(int, int); char *read_command(void); char *read_string(char, int); void include_start(char *); int include_finish(void); #define lex_getc() getc(parse_file->f) #define lex_ungetc(ch) ungetc(ch, parse_file->f) struct token { const char *name; int value; }; static const struct token tokens[] = { { "B", TOKBYTES }, { "G", TOKGIGABYTES }, { "GB", TOKGIGABYTES }, { "K", TOKKILOBYTES }, { "KB", TOKKILOBYTES }, { "M", TOKMEGABYTES }, { "MB", TOKMEGABYTES }, { "account", TOKACCOUNT }, { "accounts", TOKACCOUNTS }, { "action", TOKACTION }, { "actions", TOKACTIONS }, { "add-header", TOKADDHEADER }, { "add-to-cache", TOKADDTOCACHE }, { "age", TOKAGE }, { "all", TOKALL }, { "allow-multiple", TOKALLOWMANY }, { "and", TOKAND }, { "any-name", TOKANYNAME }, { "any-size", TOKANYSIZE }, { "any-type", TOKANYTYPE }, { "append", TOKAPPEND }, { "attachment", TOKATTACHMENT }, { "b", TOKBYTES }, { "body", TOKBODY }, { "byte", TOKBYTES }, { "bytes", TOKBYTES }, { "cache", TOKCACHE }, { "case", TOKCASE }, { "command-user", TOKCMDUSER }, { "compress", TOKCOMPRESS }, { "continue", TOKCONTINUE }, { "count", TOKCOUNT }, { "courier", TOKCOURIER }, { "day", TOKDAYS }, { "days", TOKDAYS }, { "default-user", TOKDEFUSER }, { "delete-oversized", TOKDELTOOBIG }, { "disabled", TOKDISABLED }, { "domain", TOKDOMAIN }, { "dotlock", TOKDOTLOCK }, { "drop", TOKDROP }, { "exec", TOKEXEC }, { "expire", TOKEXPIRE }, { "fcntl", TOKFCNTL }, { "file-group", TOKFILEGROUP }, { "file-umask", TOKFILEUMASK }, { "flock", TOKFLOCK }, { "folder", TOKFOLDER }, { "folders", TOKFOLDERS }, { "from", TOKFROM }, { "g", TOKGIGABYTES }, { "gb", TOKGIGABYTES }, { "gigabyte", TOKGIGABYTES }, { "gigabytes", TOKGIGABYTES }, { "group", TOKGROUP }, { "groups", TOKGROUPS }, { "header", TOKHEADER }, { "headers", TOKHEADERS }, { "hour", TOKHOURS }, { "hours", TOKHOURS }, { "imap", TOKIMAP }, { "imaps", TOKIMAPS }, { "in", TOKIN }, { "in-cache", TOKINCACHE }, { "invalid", TOKINVALID }, { "k", TOKKILOBYTES }, { "kb", TOKKILOBYTES }, { "keep", TOKKEEP }, { "key", TOKKEY }, { "kilobyte", TOKKILOBYTES }, { "kilobytes", TOKKILOBYTES }, { "lock-file", TOKLOCKFILE }, { "lock-type", TOKLOCKTYPES }, { "lock-types", TOKLOCKTYPES }, { "lookup-order", TOKLOOKUPORDER }, { "m", TOKMEGABYTES }, { "maildir", TOKMAILDIR }, { "maildirs", TOKMAILDIRS }, { "match", TOKMATCH }, { "matched", TOKMATCHED }, { "maximum-size", TOKMAXSIZE }, { "mb", TOKMEGABYTES }, { "mbox", TOKMBOX }, { "mboxes", TOKMBOXES }, { "megabyte", TOKMEGABYTES }, { "megabytes", TOKMEGABYTES }, { "minute", TOKMINUTES }, { "minutes", TOKMINUTES }, { "month", TOKMONTHS }, { "months", TOKMONTHS }, { "new-only", TOKNEWONLY }, { "nntp", TOKNNTP }, { "nntps", TOKNNTPS }, { "no-apop", TOKNOAPOP }, { "no-cram-md5", TOKNOCRAMMD5 }, { "no-create", TOKNOCREATE }, { "no-login", TOKNOLOGIN }, { "no-received", TOKNORECEIVED }, { "no-tls1", TOKNOTLS1 }, { "no-uidl", TOKNOUIDL }, { "no-verify", TOKNOVERIFY }, { "none", TOKNONE }, { "not", TOKNOT }, { "old-only", TOKOLDONLY }, { "or", TOKOR }, { "parallel-accounts", TOKPARALLELACCOUNTS }, { "pass", TOKPASS }, { "passwd", TOKPASSWD }, { "pipe", TOKPIPE }, { "pop3", TOKPOP3 }, { "pop3s", TOKPOP3S }, { "port", TOKPORT }, { "proxy", TOKPROXY }, { "purge-after", TOKPURGEAFTER }, { "queue-high", TOKQUEUEHIGH }, { "queue-low", TOKQUEUELOW }, { "remove-from-cache", TOKREMOVEFROMCACHE }, { "remove-header", TOKREMOVEHEADER }, { "remove-headers", TOKREMOVEHEADERS }, { "returns", TOKRETURNS }, { "rewrite", TOKREWRITE }, { "second", TOKSECONDS }, { "seconds", TOKSECONDS }, { "server", TOKSERVER }, { "set", TOKSET }, { "size", TOKSIZE }, { "smtp", TOKSMTP }, { "stdin", TOKSTDIN }, { "stdout", TOKSTDOUT }, { "string", TOKSTRING }, { "strip-characters", TOKSTRIPCHARACTERS }, { "tag", TOKTAG }, { "tagged", TOKTAGGED }, { "timeout", TOKTIMEOUT }, { "to", TOKTO }, { "to-cache", TOKADDTOCACHE }, { "total-size", TOKTOTALSIZE }, { "unmatched", TOKUNMATCHED }, { "unmatched-mail", TOKIMPLACT }, { "user", TOKUSER }, { "users", TOKUSERS }, { "value", TOKVALUE }, { "verify-certificates", TOKVERIFYCERTS }, { "week", TOKWEEKS }, { "weeks", TOKWEEKS }, { "write", TOKWRITE }, { "year", TOKYEARS }, { "years", TOKYEARS } }; int yylex(void) { int ch, value; char *path; struct replpath rp; /* Switch to new file. See comment in read_token below. */ if (lex_include) { while ((ch = lex_getc()) != EOF && isspace((u_char) ch)) ; if (ch != '"' && ch != '\'') yyerror("syntax error"); if (ch == '"') rp.str = read_string('"', 1); else rp.str = read_string('\'', 0); path = replacepath(&rp, parse_tags, NULL, NULL, conf.user_home); xfree(rp.str); include_start(path); lex_include = 0; } restart: while ((ch = lex_getc()) != EOF) { switch (ch) { case '#': /* Comment: discard until EOL. */ while ((ch = lex_getc()) != '\n' && ch != EOF) ; parse_file->line++; break; case '\'': yylval.string = read_string('\'', 0); value = STRING; goto out; case '"': yylval.string = read_string('"', 1); value = STRING; goto out; case '$': ch = lex_getc(); if (ch == '(') { yylval.string = read_command(); value = STRCOMMAND; goto out; } if (ch == '{' || isalnum((u_char) ch)) { yylval.string = read_macro('$', ch); value = STRMACRO; goto out; } yyerror("invalid macro name"); case '%': ch = lex_getc(); if (ch == '(') { yylval.string = read_command(); value = NUMCOMMAND; goto out; } if (ch == '{' || isalnum((u_char) ch)) { yylval.string = read_macro('%', ch); value = NUMMACRO; goto out; } yyerror("invalid macro name"); case '=': ch = lex_getc(); if (ch == '=') { value = TOKEQ; goto out; } lex_ungetc(ch); value = '='; goto out; case '!': ch = lex_getc(); if (ch == '=') { value = TOKNE; goto out; } lex_ungetc(ch); value = '!'; goto out; case '~': case '+': case '(': case ')': case ',': case '<': case '>': case '{': case '}': case '*': value = ch; goto out; case '\n': parse_file->line++; break; case ' ': case '\t': break; default: if (ch != '_' && ch != '-' && !isalnum((u_char) ch)) yyerror("unexpected character: %c", ch); if (isdigit((u_char) ch)) { yylval.number = read_number(ch); value = NUMBER; goto out; } value = read_token(ch); goto out; } } if (!include_finish()) goto restart; if (lex_ifdef != 0) yyerror("missing endif"); return (EOF); out: if (lex_skip) goto restart; return (value); } int cmp_token(const void *name, const void *ptr) { const struct token *token = ptr; return (strcmp(name, token->name)); } int read_token(int ch) { int ch2; char token[128], *name; size_t tlen; struct token *ptr; struct macro *macro; tlen = 0; token[tlen++] = ch; while ((ch = lex_getc()) != EOF) { if (!isalnum((u_char) ch) && ch != '-' && ch != '_') break; token[tlen++] = ch; if (tlen == (sizeof token) - 1) yyerror("token too long"); } token[tlen] = '\0'; lex_ungetc(ch); /* * ifdef/ifndef/endif is special-cased here since it is really really * hard to make work with yacc. */ if (strcmp(token, "ifdef") == 0 || strcmp(token, "ifndef") == 0) { while ((ch = lex_getc()) != EOF && isspace((u_char) ch)) ; if (ch != '$' && ch != '%') yyerror("syntax error"); ch2 = lex_getc(); if (ch2 != '{' && !isalnum((u_char) ch2)) yyerror("invalid macro name"); name = read_macro(ch, ch2); macro = find_macro(name); xfree(name); if (token[2] == 'n' && macro != NULL) lex_skip = 1; if (token[2] != 'n' && macro == NULL) lex_skip = 1; lex_ifdef++; return (NONE); } if (strcmp(token, "endif") == 0) { if (lex_ifdef == 0) yyerror("spurious endif"); lex_ifdef--; if (lex_ifdef == 0) lex_skip = 0; return (NONE); } if (strcmp(token, "include") == 0) { /* * This is a bit strange. * * yacc may have symbols buffered and be waiting for more to * decide which production to match, so we can't just switch * file now. So, we set a flag that tells yylex to switch files * next time it's called and return the NONE symbol. This is a * placeholder not used in any real productions, so it should * cause yacc to match using whatever it has (assuming it * can). If we don't do this, there are problems with things * like: * * $file = "abc" * include "${file}" * * The include token is seen before yacc has matched the * previous line, so the macro doesn't exist when we try to * build the include file path. */ lex_include = 1; return (NONE); } ptr = bsearch(token, tokens, (sizeof tokens)/(sizeof tokens[0]), sizeof tokens[0], cmp_token); if (ptr == NULL) yyerror("unknown token: %s", token); return (ptr->value); } long long read_number(int ch) { char number[32]; size_t nlen; const char *errstr; long long n; nlen = 0; number[nlen++] = ch; while ((ch = lex_getc()) != EOF) { if (!isdigit((u_char) ch)) break; number[nlen++] = ch; if (nlen == (sizeof number) - 1) yyerror("number too long"); } number[nlen] = '\0'; lex_ungetc(ch); n = strtonum(number, 0, LLONG_MAX, &errstr); if (errstr != NULL) yyerror("number is %s", errstr); return (n); } char * read_macro(int type, int ch) { char name[MAXNAMESIZE]; size_t nlen; int brackets; brackets = 0; if (ch == '{') { ch = lex_getc(); if (!isalnum((u_char) ch)) yyerror("invalid macro name"); brackets = 1; } nlen = 0; name[nlen++] = type; name[nlen++] = ch; while ((ch = lex_getc()) != EOF) { if (!isalnum((u_char) ch) && ch != '-' && ch != '_') break; name[nlen++] = ch; if (nlen == (sizeof name) - 1) yyerror("macro name too long"); } name[nlen] = '\0'; if (!brackets) lex_ungetc(ch); if (brackets && ch != '}') yyerror("missing }"); if (*name == '\0') yyerror("empty macro name"); return (xstrdup(name)); } char * read_command(void) { int ch, nesting; size_t pos = 0, len, slen; char *buf, *s; len = 24; buf = xmalloc(len + 1); nesting = 0; while ((ch = lex_getc()) != EOF) { switch (ch) { case '(': nesting++; break; case ')': if (nesting == 0) { buf[pos] = '\0'; return (buf); } nesting--; break; case '"': s = read_string('"', 1); slen = strlen(s); ENSURE_SIZE(buf, len, pos + slen + 2); buf[pos++] = '"'; memcpy(buf + pos, s, slen); pos += slen; buf[pos++] = '"'; xfree(s); continue; case '\'': s = read_string('\'', 0); slen = strlen(s); ENSURE_SIZE(buf, len, pos + slen + 2); buf[pos++] = '\''; memcpy(buf + pos, s, slen); pos += slen; buf[pos++] = '\''; xfree(s); continue; } buf[pos++] = ch; ENSURE_SIZE(buf, len, pos); } yyerror("missing )"); } char * read_string(char endch, int esc) { int ch, oldch; size_t pos, len, slen; char *name, *s, *buf; struct macro *macro; len = 24; buf = xmalloc(len + 1); pos = 0; while ((ch = lex_getc()) != endch) { switch (ch) { case EOF: yyerror("missing %c", endch); case '\\': if (!esc) break; switch (ch = lex_getc()) { case EOF: yyerror("missing %c", endch); case 'r': ch = '\r'; break; case 'n': ch = '\n'; break; case 't': ch = '\t'; break; } break; case '$': case '%': if (!esc) break; oldch = ch; ch = lex_getc(); if (ch == EOF) yyerror("missing %c", endch); if (ch != '{') { lex_ungetc(ch); ch = oldch; break; } name = read_macro(oldch, '{'); if ((macro = find_macro(name)) == NULL) { xfree(name); continue; } xfree(name); if (macro->type == MACRO_NUMBER) xasprintf(&s, "%lld", macro->value.num); else s = macro->value.str; slen = strlen(s); ENSURE_FOR(buf, len, pos, slen + 1); memcpy(buf + pos, s, slen); pos += slen; if (macro->type == MACRO_NUMBER) xfree(s); continue; } buf[pos++] = ch; ENSURE_SIZE(buf, len, pos); } buf[pos] = '\0'; return (buf); } void include_start(char *file) { char *path; FILE *f; struct stat sb; if (*file == '\0') yyerror("invalid include file"); if ((f = fopen(file, "r")) == NULL) { xasprintf(&path, "%s/%s", xdirname(conf.conf_file), file); if ((f = fopen(path, "r")) == NULL) yyerror("%s: %s", path, strerror(errno)); xfree(file); } else path = file; if (fstat(fileno(f), &sb) != 0) yyerror("%s: %s", path, strerror(errno)); if (geteuid() != 0 && (sb.st_mode & (S_IROTH|S_IWOTH)) != 0) log_warnx("%s: world readable or writable", path); ARRAY_ADD(&parse_filestack, parse_file); parse_file = xmalloc(sizeof *parse_file); parse_file->f = f; parse_file->line = 1; parse_file->path = path; log_debug2("including file %s", parse_file->path); } int include_finish(void) { if (ARRAY_EMPTY(&parse_filestack)) return (1); log_debug2("finished file %s", parse_file->path); xfree(parse_file); parse_file = ARRAY_LAST(&parse_filestack); ARRAY_TRUNC(&parse_filestack, 1); return (0); } fdm-1.7+cvs20140912/fetch.h0000600000175000017500000001433311437305221013443 0ustar hawkhawk/* $Id: fetch.h,v 1.56 2010/08/31 23:04:49 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef FETCH_H #define FETCH_H /* Fetch return codes. */ #define FETCH_AGAIN 1 #define FETCH_BLOCK 2 #define FETCH_ERROR 3 #define FETCH_MAIL 4 #define FETCH_EXIT 5 /* Fetch flags. */ #define FETCH_PURGE 0x1 #define FETCH_EMPTY 0x2 #define FETCH_POLL 0x4 /* Fetch context. */ struct fetch_ctx { int (*state)(struct account *, struct fetch_ctx *); int flags; struct mail *mail; size_t llen; char *lbuf; }; /* Fetch functions. */ struct fetch { const char *name; int (*first)(struct account *, struct fetch_ctx *); void (*fill)(struct account *, struct iolist *); int (*commit)(struct account *, struct mail *); void (*abort)(struct account *); u_int (*total)(struct account *); void (*desc)(struct account *, char *, size_t); }; /* Ranges of mail. */ enum fetch_only { FETCH_ONLY_NEW, FETCH_ONLY_OLD, FETCH_ONLY_ALL }; /* Fetch maildir data. */ struct fetch_maildir_data { struct strings *maildirs; u_int total; struct strings unlinklist; struct strings *paths; u_int index; DIR *dirp; }; struct fetch_maildir_mail { char path[MAXPATHLEN]; }; /* Fetch mbox data. */ struct fetch_mbox_data { struct strings *mboxes; ARRAY_DECL(, struct fetch_mbox_mbox *) fmboxes; u_int index; size_t off; TAILQ_HEAD(, fetch_mbox_mail) kept; }; struct fetch_mbox_mbox { char *path; u_int reference; u_int total; int fd; char *base; size_t size; }; struct fetch_mbox_mail { size_t off; size_t size; struct fetch_mbox_mbox *fmbox; TAILQ_ENTRY(fetch_mbox_mail) entry; }; /* NNTP group entry. */ struct fetch_nntp_group { char *name; int ignore; u_int size; u_int last; char *id; }; /* Fetch nntp data. */ struct fetch_nntp_data { char *path; char *user; char *pass; struct server server; struct strings *names; u_int group; ARRAY_DECL(, struct fetch_nntp_group *) groups; int flushing; struct io *io; }; /* Fetch pop3 queues and trees. */ TAILQ_HEAD(fetch_pop3_queue, fetch_pop3_mail); SPLAY_HEAD(fetch_pop3_tree, fetch_pop3_mail); /* Fetch pop3 data. */ struct fetch_pop3_data { char *path; enum fetch_only only; char *user; char *pass; struct server server; char *pipecmd; int apop; int uidl; u_int cur; u_int num; u_int total; u_int committed; /* Mails on the server. */ struct fetch_pop3_tree serverq; /* Mails in the cache file. */ struct fetch_pop3_tree cacheq; /* Mails to fetch from the server. */ struct fetch_pop3_queue wantq; /* Mails ready to be dropped. */ struct fetch_pop3_queue dropq; int flushing; size_t size; struct io *io; struct cmd *cmd; char *src; int (*connect)(struct account *); void (*disconnect)(struct account *); int (*getln)( struct account *, struct fetch_ctx *, char **); int (*putln)(struct account *, const char *, va_list); }; struct fetch_pop3_mail { char *uid; u_int idx; TAILQ_ENTRY(fetch_pop3_mail) qentry; SPLAY_ENTRY(fetch_pop3_mail) tentry; }; /* Fetch imap data. */ struct fetch_imap_data { enum fetch_only only; char *user; char *pass; struct server server; char *pipecmd; int nocrammd5; int nologin; u_int folder; struct strings *folders; u_int folders_total; /* total mail count */ int capa; int tag; ARRAY_DECL(, u_int) wanted; ARRAY_DECL(, u_int) dropped; ARRAY_DECL(, u_int) kept; u_int total; u_int committed; int flushing; size_t size; u_int lines; struct io *io; struct cmd *cmd; char *src; int (*connect)(struct account *); void (*disconnect)(struct account *); int (*getln)( struct account *, struct fetch_ctx *, char **); int (*putln)(struct account *, const char *, va_list); }; struct fetch_imap_mail { u_int uid; }; #define IMAP_TAG_NONE -1 #define IMAP_TAG_CONTINUE -2 #define IMAP_TAG_ERROR -3 #define IMAP_TAGGED 0 #define IMAP_CONTINUE 1 #define IMAP_UNTAGGED 2 #define IMAP_RAW 3 #define IMAP_CAPA_AUTH_CRAM_MD5 0x1 #define IMAP_CAPA_XYZZY 0x2 /* fetch-maildir.c */ extern struct fetch fetch_maildir; /* fetch-mbx.c */ extern struct fetch fetch_mbox; /* fetch-stdin.c */ extern struct fetch fetch_stdin; /* fetch-nntp.c */ extern struct fetch fetch_nntp; /* fetch-pop3.c */ extern struct fetch fetch_pop3; /* fetch-pop3pipe.c */ extern struct fetch fetch_pop3pipe; /* fetch-imap.c */ extern struct fetch fetch_imap; int fetch_imap_putln(struct account *, const char *, va_list); int fetch_imap_getln(struct account *, struct fetch_ctx *, char **); int fetch_imap_state_init(struct account *, struct fetch_ctx *); /* fetch-imappipe.c */ extern struct fetch fetch_imappipe; /* imap-common.c */ int imap_tag(char *); int imap_putln(struct account *, const char *, ...); int imap_getln(struct account *, struct fetch_ctx *, int, char **); int imap_okay(char *); int imap_no(char *); int imap_bad(struct account *, const char *); int imap_invalid(struct account *, const char *); int imap_state_init(struct account *, struct fetch_ctx *); int imap_state_connected(struct account *, struct fetch_ctx *); int imap_state_select1(struct account *, struct fetch_ctx *); int imap_commit(struct account *, struct mail *); void imap_abort(struct account *); u_int imap_total(struct account *); /* pop3-common.c */ int pop3_state_init(struct account *, struct fetch_ctx *); int pop3_commit(struct account *, struct mail *); void pop3_abort(struct account *); u_int pop3_total(struct account *); #endif fdm-1.7+cvs20140912/match-in-cache.c0000600000175000017500000000426210657120225015110 0ustar hawkhawk/* $Id: match-in-cache.c,v 1.6 2007/08/10 17:29:57 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_in_cache_match(struct mail_ctx *, struct expritem *); void match_in_cache_desc(struct expritem *, char *, size_t); struct match match_in_cache = { "in-cache", match_in_cache_match, match_in_cache_desc }; int match_in_cache_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_in_cache_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; char *key; struct cache *cache; key = replacestr(&data->key, m->tags, m, &m->rml); if (key == NULL || *key == '\0') { log_warnx("%s: empty key", a->name); goto error; } log_debug2("%s: matching to cache %s: %s", a->name, data->path, key); TAILQ_FOREACH(cache, &conf.caches, entry) { if (strcmp(data->path, cache->path) == 0) { if (open_cache(a, cache) != 0) goto error; if (db_contains(cache->db, key)) { xfree(key); return (MATCH_TRUE); } xfree(key); return (MATCH_FALSE); } } log_warnx("%s: cache %s not declared", a->name, data->path); error: if (key != NULL) xfree(key); return (MATCH_ERROR); } void match_in_cache_desc(struct expritem *ei, char *buf, size_t len) { struct match_in_cache_data *data = ei->data; xsnprintf(buf, len, "in-cache \"%s\" key \"%s\"", data->path, data->key.str); } fdm-1.7+cvs20140912/index.html.in0000600000175000017500000000110510653605565014611 0ustar hawkhawk fdm

The project page is here.

Download fdm %%VERSION%% with this link.

A mailing list is available, see this page.


&&MANUAL

%%fdm.1.html
%%fdm.conf.5.html fdm-1.7+cvs20140912/deliver-imap.c0000600000175000017500000002032311437305220014716 0ustar hawkhawk/* $Id: deliver-imap.c,v 1.4 2010/08/31 23:04:48 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" #include "fetch.h" /* * This file is a bit of a mishmash, so that we can use some bits from * the IMAP fetching code. * * All needs to be straightened out sometime. */ int deliver_imap_deliver(struct deliver_ctx *, struct actitem *); void deliver_imap_desc(struct actitem *, char *, size_t); int deliver_imap_poll(struct account *, struct io *); int deliver_imap_pollto(int (*)(struct account *, struct fetch_ctx *), struct account *, struct io *, struct fetch_ctx *); int deliver_imap_waitokay(struct account *, struct fetch_ctx *, struct io *, char **); int deliver_imap_waitcontinue(struct account *, struct fetch_ctx *, struct io *, char **); int deliver_imap_waitappend(struct account *, struct fetch_ctx *, struct io *, char **); struct deliver deliver_imap = { "imap", DELIVER_ASUSER, deliver_imap_deliver, deliver_imap_desc }; /* Poll for data from/to server. */ int deliver_imap_poll(struct account *a, struct io *io) { char *cause; switch (io_poll(io, conf.timeout, &cause)) { case 0: log_warnx("%s: connection unexpectedly closed", a->name); return (1); case -1: log_warnx("%s: %s", a->name, cause); xfree(cause); return (1); } return (0); } /* Poll through the IMAP fetch states until a particular one is reached. */ int deliver_imap_pollto(int (*state)(struct account *, struct fetch_ctx *), struct account *a, struct io *io, struct fetch_ctx *fctx) { while (state == NULL || fctx->state != state) { switch (fctx->state(a, fctx)) { case FETCH_AGAIN: continue; case FETCH_ERROR: return (1); case FETCH_EXIT: return (0); } if (deliver_imap_poll(a, io) != 0) return (1); } return (0); } /* Wait for okay. */ int deliver_imap_waitokay(struct account *a, struct fetch_ctx *fctx, struct io *io, char **line) { do { if (deliver_imap_poll(a, io) != 0) return (1); if (imap_getln(a, fctx, IMAP_TAGGED, line) != 0) return (1); } while (*line == NULL); if (!imap_okay(*line)) { imap_bad(a, *line); return (1); } return (0); } /* Wait for continuation. */ int deliver_imap_waitcontinue(struct account *a, struct fetch_ctx *fctx, struct io *io, char **line) { do { if (deliver_imap_poll(a, io) != 0) return (1); if (imap_getln(a, fctx, IMAP_CONTINUE, line) != 0) return (1); } while (*line == NULL); return (0); } /* Wait for append response. */ int deliver_imap_waitappend(struct account *a, struct fetch_ctx *fctx, struct io *io, char **line) { struct fetch_imap_data *data = a->data; int tag; for (;;) { if (deliver_imap_poll(a, io) != 0) { line = NULL; return (IMAP_TAG_ERROR); } if (data->getln(a, fctx, line) != 0) { line = NULL; return (IMAP_TAG_ERROR); } if (*line == NULL) continue; tag = imap_tag(*line); if (tag != IMAP_TAG_NONE) break; } if (tag == IMAP_TAG_CONTINUE) return (IMAP_TAG_CONTINUE); if (tag != data->tag) return (IMAP_TAG_ERROR); return (tag); } int deliver_imap_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_imap_data *data = ti->data; struct io *io; struct fetch_ctx fctx; struct fetch_imap_data fdata; char *cause, *folder, *ptr, *line; size_t len, maillen; u_int total, body; /* Connect to the IMAP server. */ io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (DELIVER_FAILURE); } if (conf.debug > 3 && !conf.syslog) io->dup_fd = STDOUT_FILENO; /* Work out the folder name. */ folder = replacestr(&data->folder, m->tags, m, &m->rml); if (folder == NULL || *folder == '\0') { log_warnx("%s: empty folder", a->name); goto error; } /* Fake up the fetch context for the fetch code. */ memset(&fdata, 0, sizeof fdata); fdata.user = data->user; fdata.pass = data->pass; fdata.nocrammd5 = data->nocrammd5; fdata.nologin = data->nologin; memcpy(&fdata.server, &data->server, sizeof fdata.server); fdata.io = io; fdata.only = FETCH_ONLY_ALL; a->data = &fdata; fetch_imap_state_init(a, &fctx); fctx.state = imap_state_connected; fctx.llen = IO_LINESIZE; fctx.lbuf = xmalloc(fctx.llen); /* Use the fetch code until the select1 state is reached. */ if (deliver_imap_pollto(imap_state_select1, a, io, &fctx) != 0) goto error; retry: /* Send an append command. */ if (imap_putln(a, "%u APPEND {%zu}", ++fdata.tag, strlen(folder)) != 0) goto error; switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: if (line != NULL) imap_invalid(a, line); goto error; case IMAP_TAG_CONTINUE: break; default: if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } /* * Send the mail size, not forgetting lines are CRLF terminated. The * Google IMAP server is written strangely, so send the size as if * every CRLF was a CR if the server has XYZZY. */ count_lines(m, &total, &body); maillen = m->size + total - 1; if (fdata.capa & IMAP_CAPA_XYZZY) { log_debug2("%s: adjusting size: actual %zu", a->name, maillen); maillen = m->size; } if (imap_putln(a, "%s {%zu}", folder, maillen) != 0) goto error; switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: if (line != NULL) imap_invalid(a, line); goto error; case IMAP_TAG_CONTINUE: break; default: if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } /* Send the mail data. */ line_init(m, &ptr, &len); while (ptr != NULL) { if (len > 1) io_write(io, ptr, len - 1); io_writeline(io, NULL); /* Update if necessary. */ if (io_update(io, conf.timeout, &cause) != 1) { log_warnx("%s: %s", a->name, cause); xfree(cause); goto error; } line_next(m, &ptr, &len); } /* Wait for an okay from the server. */ switch (deliver_imap_waitappend(a, &fctx, io, &line)) { case IMAP_TAG_ERROR: case IMAP_TAG_CONTINUE: if (line != NULL) imap_invalid(a, line); goto error; default: if (imap_okay(line)) break; if (strstr(line, "[TRYCREATE]") != NULL) goto try_create; imap_invalid(a, line); goto error; } xfree(fctx.lbuf); xfree(folder); if (imap_putln(a, "%u LOGOUT", ++fdata.tag) != 0) goto error; if (deliver_imap_waitokay(a, &fctx, io, &line) != 0) goto error; fdata.disconnect(a); return (DELIVER_SUCCESS); try_create: /* XXX function? */ /* Try to create the folder. */ if (imap_putln(a, "%u CREATE {%zu}", ++fdata.tag, strlen(folder)) != 0) goto error; if (deliver_imap_waitcontinue(a, &fctx, io, &line) != 0) goto error; if (imap_putln(a, "%s", folder) != 0) goto error; if (deliver_imap_waitokay(a, &fctx, io, &line) != 0) goto error; goto retry; error: io_writeline(io, "QUIT"); io_flush(io, conf.timeout, NULL); xfree(fctx.lbuf); if (folder != NULL) xfree(folder); fdata.disconnect(a); return (DELIVER_FAILURE); } void deliver_imap_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_imap_data *data = ti->data; xsnprintf(buf, len, "imap%s server \"%s\" port %s folder \"%s\"", data->server.ssl ? "s" : "", data->server.host, data->server.port, data->folder.str); } fdm-1.7+cvs20140912/pop3-common.c0000600000175000017500000005440211531423214014513 0ustar hawkhawk/* $Id: pop3-common.c,v 1.14 2011/02/24 09:36:12 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" int pop3_putln(struct account *, const char *, ...); int pop3_getln(struct account *, struct fetch_ctx *, char **); int pop3_commit(struct account *, struct mail *); void pop3_abort(struct account *); u_int pop3_total(struct account *); int pop3_load(struct account *); int pop3_save(struct account *); int pop3_cmp(struct fetch_pop3_mail *, struct fetch_pop3_mail *); void pop3_free(void *); int pop3_okay(const char *); void pop3_freequeue(struct fetch_pop3_queue *); void pop3_freetree(struct fetch_pop3_tree *); int pop3_bad(struct account *, const char *); int pop3_invalid(struct account *, const char *); int pop3_state_connect(struct account *, struct fetch_ctx *); int pop3_state_connected(struct account *, struct fetch_ctx *); int pop3_state_user(struct account *, struct fetch_ctx *); int pop3_state_cache1(struct account *, struct fetch_ctx *); int pop3_state_cache2(struct account *, struct fetch_ctx *); int pop3_state_cache3(struct account *, struct fetch_ctx *); int pop3_state_stat(struct account *, struct fetch_ctx *); int pop3_state_first(struct account *, struct fetch_ctx *); int pop3_state_next(struct account *, struct fetch_ctx *); int pop3_state_delete(struct account *, struct fetch_ctx *); int pop3_state_reconnect(struct account *, struct fetch_ctx *); int pop3_state_list(struct account *, struct fetch_ctx *); int pop3_state_uidl(struct account *, struct fetch_ctx *); int pop3_state_retr(struct account *, struct fetch_ctx *); int pop3_state_line(struct account *, struct fetch_ctx *); int pop3_state_quit(struct account *, struct fetch_ctx *); SPLAY_PROTOTYPE(fetch_pop3_tree, fetch_pop3_mail, tentry, pop3_cmp); SPLAY_GENERATE(fetch_pop3_tree, fetch_pop3_mail, tentry, pop3_cmp); /* Put line to server. */ int pop3_putln(struct account *a, const char *fmt, ...) { struct fetch_pop3_data *data = a->data; va_list ap; int n; va_start(ap, fmt); n = data->putln(a, fmt, ap); va_end(ap); return (n); } /* Get line from server. Returns -1 on error, a NULL line if out of data. */ int pop3_getln(struct account *a, struct fetch_ctx *fctx, char **line) { struct fetch_pop3_data *data = a->data; if (data->getln(a, fctx, line) != 0) return (-1); return (0); } int pop3_cmp(struct fetch_pop3_mail *aux1, struct fetch_pop3_mail *aux2) { return (strcmp(aux1->uid, aux2->uid)); } /* * Free a POP3 mail aux structure. All such structures are always on at least * one queue or tree so this is always called explicitly rather than via the * mail auxfree member. */ void pop3_free(void *ptr) { struct fetch_pop3_mail *aux = ptr; if (aux->uid != NULL) xfree(aux->uid); xfree(aux); } int pop3_okay(const char *line) { return (strncmp(line, "+OK", 3) == 0); } void pop3_freequeue(struct fetch_pop3_queue *q) { struct fetch_pop3_mail *aux; while (!TAILQ_EMPTY(q)) { aux = TAILQ_FIRST(q); TAILQ_REMOVE(q, aux, qentry); pop3_free(aux); } } void pop3_freetree(struct fetch_pop3_tree *t) { struct fetch_pop3_mail *aux; while (!SPLAY_EMPTY(t)) { aux = SPLAY_ROOT(t); SPLAY_REMOVE(fetch_pop3_tree, t, aux); pop3_free(aux); } } int pop3_bad(struct account *a, const char *line) { log_warnx("%s: unexpected data: %s", a->name, line); return (FETCH_ERROR); } int pop3_invalid(struct account *a, const char *line) { log_warnx("%s: invalid response: %s", a->name, line); return (FETCH_ERROR); } /* Load POP3 cache file into the cache queue. */ int pop3_load(struct account *a) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux; int fd; FILE *f = NULL; char *uid; size_t uidlen; u_int n; if (data->path == NULL) return (0); if ((fd = openlock(data->path, O_RDONLY, conf.lock_types)) == -1) { if (errno == ENOENT) return (0); log_warn("%s: %s", a->name, data->path); goto error; } if ((f = fdopen(fd, "r")) == NULL) { log_warn("%s: %s", a->name, data->path); goto error; } n = 0; for (;;) { if (fscanf(f, "%zu ", &uidlen) != 1) { /* EOF is allowed only at the start of a line. */ if (feof(f)) break; goto invalid; } uid = xmalloc(uidlen + 1); if (fread(uid, uidlen, 1, f) != 1) goto invalid; uid[uidlen] = '\0'; log_debug3("%s: found UID in cache: %s", a->name, uid); aux = xcalloc(1, sizeof *aux); aux->uid = uid; SPLAY_INSERT(fetch_pop3_tree, &data->cacheq, aux); } fclose(f); closelock(fd, data->path, conf.lock_types); return (0); invalid: log_warnx("%s: invalid cache entry", a->name); error: if (f != NULL) fclose(f); if (fd != -1) closelock(fd, data->path, conf.lock_types); return (-1); } /* Save POP3 cache file. */ int pop3_save(struct account *a) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux; char *path = NULL, tmp[MAXPATHLEN]; int fd = -1; FILE *f = NULL; u_int n; if (data->path == NULL) return (0); if (ppath(tmp, sizeof tmp, "%s.XXXXXXXXXX", data->path) != 0) goto error; if ((fd = mkstemp(tmp)) == -1) goto error; path = tmp; cleanup_register(path); if ((f = fdopen(fd, "r+")) == NULL) goto error; fd = -1; n = 0; SPLAY_FOREACH(aux, fetch_pop3_tree, &data->cacheq) { fprintf(f, "%zu %s\n", strlen(aux->uid), aux->uid); n++; } log_debug2("%s: saved cache %s: %u entries", a->name, data->path, n); if (fflush(f) != 0) goto error; if (fsync(fileno(f)) != 0) goto error; fclose(f); f = NULL; if (rename(path, data->path) == -1) goto error; cleanup_deregister(path); return (0); error: log_warn("%s: %s", a->name, data->path); if (f != NULL) fclose(f); if (fd != -1) close(fd); if (path != NULL) { if (unlink(tmp) != 0) fatal("unlink failed"); cleanup_deregister(path); } return (-1); } /* Commit mail. */ int pop3_commit(struct account *a, struct mail *m) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux = m->auxdata; if (m->decision == DECISION_DROP) { /* Insert to tail of the drop queue; reading is from head. */ TAILQ_INSERT_TAIL(&data->dropq, aux, qentry); m->auxdata = NULL; } else { /* If not already in the cache, add it. */ if (SPLAY_FIND(fetch_pop3_tree, &data->cacheq, aux) == NULL) SPLAY_INSERT(fetch_pop3_tree, &data->cacheq, aux); else pop3_free(aux); m->auxdata = NULL; data->committed++; if (data->only != FETCH_ONLY_OLD && pop3_save(a) != 0) return (FETCH_ERROR); } return (FETCH_AGAIN); } /* Close and free everything. Used for abort and after quit. */ void pop3_abort(struct account *a) { struct fetch_pop3_data *data = a->data; pop3_freetree(&data->serverq); pop3_freetree(&data->cacheq); pop3_freequeue(&data->wantq); pop3_freequeue(&data->dropq); data->disconnect(a); } /* Return total mails. */ u_int pop3_total(struct account *a) { struct fetch_pop3_data *data = a->data; return (data->total); } /* * Initial state. This is separate from connect as it is necessary to reconnect * without wiping the dropped/kept list and counters. */ int pop3_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; SPLAY_INIT(&data->serverq); SPLAY_INIT(&data->cacheq); TAILQ_INIT(&data->wantq); TAILQ_INIT(&data->dropq); data->total = data->committed = 0; fctx->state = pop3_state_connect; return (FETCH_AGAIN); } /* Connect state. */ int pop3_state_connect(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; if (data->connect(a) != 0) return (FETCH_ERROR); fctx->state = pop3_state_connected; return (FETCH_BLOCK); } /* Connected state: wait for initial +OK line from server. */ int pop3_state_connected(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; char *line, *ptr, *src; char out[MD5_DIGEST_LENGTH * 2 + 1]; u_char digest[MD5_DIGEST_LENGTH]; u_int i; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); if (data->apop && (line = strchr(line, '<')) != NULL) { if ((ptr = strchr(line + 1, '>')) != NULL) { *++ptr = '\0'; xasprintf(&src, "%s%s", line, data->pass); MD5(src, strlen(src), digest); xfree(src); for (i = 0; i < MD5_DIGEST_LENGTH; i++) xsnprintf(out + i * 2, 3, "%02hhx", digest[i]); if (pop3_putln(a, "APOP %s %s", data->user, out) != 0) return (FETCH_ERROR); fctx->state = pop3_state_stat; return (FETCH_BLOCK); } } if (pop3_putln(a, "USER %s", data->user) != 0) return (FETCH_ERROR); fctx->state = pop3_state_user; return (FETCH_BLOCK); } /* User state. */ int pop3_state_user(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); if (pop3_putln(a, "PASS %s", data->pass) != 0) return (FETCH_ERROR); fctx->state = pop3_state_stat; return (FETCH_BLOCK); } /* Stat state. */ int pop3_state_stat(struct account *a, struct fetch_ctx *fctx) { char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); if (pop3_putln(a, "STAT") != 0) return (FETCH_ERROR); fctx->state = pop3_state_first; return (FETCH_BLOCK); } /* First state. Wait for +OK then switch to get first mail. */ int pop3_state_first(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux; char *line; u_int n; u_int i; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); if (sscanf(line, "+OK %u %u", &data->num, &n) != 2) return (pop3_invalid(a, line)); data->cur = 0; /* * If no mail, we can skip UIDL and either quit (if polling or not * reconnecting) or skip to wait in next state. */ if (data->num == 0) { if (data->total != 0) { fctx->state = pop3_state_next; return (FETCH_AGAIN); } if (pop3_putln(a, "QUIT") != 0) return (FETCH_ERROR); fctx->state = pop3_state_quit; return (FETCH_BLOCK); } if (!data->uidl) { /* * Broken pop3, directly create wantq instead of using UIDL * result. */ for (i = 1; i <= data->num; i++) { aux = xcalloc(1, sizeof *aux); aux->idx = i; aux->uid = xstrdup(""); TAILQ_INSERT_TAIL(&data->wantq, aux, qentry); data->total++; } fctx->state = pop3_state_next; return (FETCH_AGAIN); } if (pop3_putln(a, "UIDL") != 0) return (FETCH_ERROR); fctx->state = pop3_state_cache1; return (FETCH_BLOCK); } /* Cache state 1. */ int pop3_state_cache1(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); /* Free the server queue. */ pop3_freetree(&data->serverq); fctx->state = pop3_state_cache2; return (FETCH_AGAIN); } /* Cache state 2. */ int pop3_state_cache2(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux; char *line, *ptr; u_int n; /* Parse response and add to server queue. */ while (data->cur != data->num) { if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "%u %*s", &n) != 1) return (pop3_invalid(a, line)); if (n != data->cur + 1) return (pop3_bad(a, line)); line = strchr(line, ' ') + 1; /* * Check UID validity. We are intolerant about validity since * accepting bad UIDs could potentially end up with UIDs that * conflict. */ if (*line == '\0') { log_warnx("%s: empty UID", a->name); return (FETCH_ERROR); } for (ptr = line; *ptr != '\0'; ptr++) { if (*ptr < 0x21 || *ptr > 0x7e) { log_warnx("%s: invalid UID: %s", a->name, line); return (FETCH_ERROR); } } if (ptr > line + 70) { log_warnx("%s: UID too big: %s", a->name, line); return (FETCH_ERROR); } aux = xcalloc(1, sizeof *aux); aux->idx = n; aux->uid = xstrdup(line); /* * If this is already in the queue, the mailbox has multiple * identical messages. This is one of the more stupid aspects * of the POP3 protocol (a unique id that isn't unique? great!). * At the moment we just abort with an error. * XXX what can we do to about this? */ if (SPLAY_FIND(fetch_pop3_tree, &data->serverq, aux)) { xfree(aux); log_warnx("%s: UID collision: %s", a->name, line); return (FETCH_ERROR); } SPLAY_INSERT(fetch_pop3_tree, &data->serverq, aux); data->cur++; } fctx->state = pop3_state_cache3; return (FETCH_AGAIN); } /* Cache state 3. */ int pop3_state_cache3(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux1, *aux2, *aux3, *before; char *line; u_int n; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (line[0] != '.' && line[1] != '\0') return (pop3_bad(a, line)); /* * Resolve the caches. * * At this point: serverq holds a list of all mails on the server and * their indexes; cacheq holds a list of all mails in the cache file; if * connecting for the first time, wantq is empty, otherwise it has the * list of mails we want. */ n = 0; if (data->total == 0) { /* * If not reconnecting, build the wantq list based on the * serverq and the cache. */ /* Load the cache and weed out any mail that doesn't exist. */ if (pop3_load(a) != 0) return (FETCH_ERROR); aux1 = SPLAY_MIN(fetch_pop3_tree, &data->cacheq); while (aux1 != NULL) { aux2 = aux1; aux1 = SPLAY_NEXT(fetch_pop3_tree, &data->cacheq, aux1); if (SPLAY_FIND( fetch_pop3_tree, &data->serverq, aux2) != NULL) continue; SPLAY_REMOVE(fetch_pop3_tree, &data->cacheq, aux2); pop3_free(aux2); } /* Build the want queue from the server queue. */ SPLAY_FOREACH(aux1, fetch_pop3_tree, &data->serverq) { switch (data->only) { case FETCH_ONLY_ALL: /* Get all mails. */ break; case FETCH_ONLY_NEW: /* Get only mails not in the cache. */ if (SPLAY_FIND(fetch_pop3_tree, &data->cacheq, aux1) != NULL) continue; break; case FETCH_ONLY_OLD: /* Get only mails in the cache. */ if (SPLAY_FIND(fetch_pop3_tree, &data->cacheq, aux1) == NULL) continue; break; } /* * Copy the mail to the want queue. Keep the want * queue sorted by UIDL order from the server. */ aux2 = xcalloc(1, sizeof *aux2); aux2->idx = aux1->idx; aux2->uid = xstrdup(aux1->uid); /* Find the first item higher than this one. */ before = NULL; TAILQ_FOREACH(aux3, &data->wantq, qentry) { if (aux3->idx > aux2->idx) { before = aux3; break; } } if (before != NULL) TAILQ_INSERT_BEFORE(before, aux2, qentry); else TAILQ_INSERT_TAIL(&data->wantq, aux2, qentry); data->total++; } /* * If there are no actual mails to fetch now, or if polling, * stop. */ if (data->total == 0 || fctx->flags & FETCH_POLL) { if (pop3_putln(a, "QUIT") != 0) return (FETCH_ERROR); fctx->state = pop3_state_quit; return (FETCH_BLOCK); } } else { /* * Reconnecting. The want queue already exists but the * indexes need to be updated from the server queue. */ aux1 = TAILQ_FIRST(&data->wantq); while (aux1 != NULL) { aux2 = aux1; aux1 = TAILQ_NEXT(aux1, qentry); /* * Check the server queue. Mails now not on the server * are removed. */ aux3 = SPLAY_FIND( fetch_pop3_tree, &data->serverq, aux2); if (aux3 == NULL) { TAILQ_REMOVE(&data->wantq, aux2, qentry); pop3_free(aux2); data->total--; } else aux2->idx = aux3->idx; } } fctx->state = pop3_state_next; return (FETCH_AGAIN); } /* Next state. */ int pop3_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct mail *m = fctx->mail; struct fetch_pop3_mail *aux; /* Handle dropped mail here. */ if (!TAILQ_EMPTY(&data->dropq)) { aux = TAILQ_FIRST(&data->dropq); if (pop3_putln(a, "DELE %u", aux->idx) != 0) return (FETCH_ERROR); fctx->state = pop3_state_delete; return (FETCH_BLOCK); } /* * If no more mail, wait until everything has been committed, then * quit. */ if (TAILQ_EMPTY(&data->wantq)) { if (data->committed != data->total) return (FETCH_BLOCK); if (pop3_putln(a, "QUIT") != 0) return (FETCH_ERROR); fctx->state = pop3_state_quit; return (FETCH_BLOCK); } /* * Try to purge if requested. This must be after dropped mail is * flushed otherwise it might use the wrong indexes after reconnect. */ if (fctx->flags & FETCH_PURGE) { /* * If can't purge now, loop through this state not fetching * mail until we can: there is no mail on the dropped queue, * and FETCH_EMPTY is set. Used to have a seperate state to * loop through without returning here, but that is wrong: * mail could potentially be added to the dropped list while * in that state. */ if (fctx->flags & FETCH_EMPTY) { fctx->flags &= ~FETCH_PURGE; if (pop3_putln(a, "QUIT") != 0) return (FETCH_ERROR); fctx->state = pop3_state_reconnect; return (FETCH_BLOCK); } /* * Must be waiting for delivery, so permit blocking even though * we (fetch) aren't waiting for any data. */ return (FETCH_BLOCK); } /* Find the next mail. */ aux = TAILQ_FIRST(&data->wantq); m->auxdata = aux; /* And list it. */ if (pop3_putln(a, "LIST %u", aux->idx) != 0) return (FETCH_ERROR); fctx->state = pop3_state_list; return (FETCH_BLOCK); } /* Delete state. */ int pop3_state_delete(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct fetch_pop3_mail *aux; char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); aux = TAILQ_FIRST(&data->dropq); /* Remove from the drop queue. */ TAILQ_REMOVE(&data->dropq, aux, qentry); /* If not already in the cache, add it. */ if (SPLAY_FIND(fetch_pop3_tree, &data->cacheq, aux) == NULL) SPLAY_INSERT(fetch_pop3_tree, &data->cacheq, aux); else pop3_free(aux); /* Update counter and save the cache. */ data->committed++; if (data->only != FETCH_ONLY_OLD && pop3_save(a) != 0) return (FETCH_ERROR); fctx->state = pop3_state_next; return (FETCH_AGAIN); } /* Reconnect state. */ int pop3_state_reconnect(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); data->disconnect(a); fctx->state = pop3_state_connect; return (FETCH_AGAIN); } /* List state. */ int pop3_state_list(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct mail *m = fctx->mail; struct fetch_pop3_mail *aux = m->auxdata; char *line; u_int n; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); if (sscanf(line, "+OK %u %zu", &n, &data->size) != 2) return (pop3_invalid(a, line)); if (n != aux->idx) return (pop3_bad(a, line)); if (pop3_putln(a, "RETR %u", aux->idx) != 0) return (FETCH_ERROR); fctx->state = pop3_state_retr; return (FETCH_BLOCK); } /* Retr state. */ int pop3_state_retr(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct mail *m = fctx->mail; struct fetch_pop3_mail *aux = m->auxdata; char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); /* Open the mail. */ if (mail_open(m, data->size) != 0) { log_warn("%s: failed to create mail", a->name); return (FETCH_ERROR); } m->size = 0; /* Tag mail. */ default_tags(&m->tags, data->src); if (data->server.host != NULL) { add_tag(&m->tags, "server", "%s", data->server.host); add_tag(&m->tags, "port", "%s", data->server.port); } add_tag(&m->tags, "server_uid", "%s", aux->uid); data->flushing = 0; fctx->state = pop3_state_line; return (FETCH_AGAIN); } /* Line state. */ int pop3_state_line(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; struct mail *m = fctx->mail; struct fetch_pop3_mail *aux = m->auxdata; char *line; for (;;) { if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (line[0] == '.') { if (line[1] == '\0') break; line++; } if (data->flushing) continue; if (append_line(m, line, strlen(line)) != 0) { log_warn("%s: failed to resize mail", a->name); return (FETCH_ERROR); } if (m->size > conf.max_size) data->flushing = 1; } /* Pull from the want queue. */ TAILQ_REMOVE(&data->wantq, aux, qentry); fctx->state = pop3_state_next; return (FETCH_MAIL); } /* Quit state. */ int pop3_state_quit(struct account *a, struct fetch_ctx *fctx) { char *line; if (pop3_getln(a, fctx, &line) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (!pop3_okay(line)) return (pop3_bad(a, line)); pop3_abort(a); return (FETCH_EXIT); } fdm-1.7+cvs20140912/deliver-add-to-cache.c0000600000175000017500000000453510700410415016203 0ustar hawkhawk/* $Id: deliver-add-to-cache.c,v 1.1 2007/10/02 09:36:13 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_add_to_cache_deliver(struct deliver_ctx *, struct actitem *); void deliver_add_to_cache_desc(struct actitem *, char *, size_t); struct deliver deliver_add_to_cache = { "add-to-cache", DELIVER_INCHILD, deliver_add_to_cache_deliver, deliver_add_to_cache_desc }; int deliver_add_to_cache_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_add_to_cache_data *data = ti->data; char *key; struct cache *cache; key = replacestr(&data->key, m->tags, m, &m->rml); if (key == NULL || *key == '\0') { log_warnx("%s: empty key", a->name); goto error; } log_debug2("%s: saving to cache %s: %s", a->name, data->path, key); TAILQ_FOREACH(cache, &conf.caches, entry) { if (strcmp(data->path, cache->path) == 0) { if (open_cache(a, cache) != 0) goto error; if (db_add(cache->db, key) != 0) { log_warnx("%s: error adding to cache %s: %s", a->name, cache->path, key); goto error; } xfree(key); return (DELIVER_SUCCESS); } } log_warnx("%s: cache %s not declared", a->name, data->path); error: if (key != NULL) xfree(key); return (DELIVER_FAILURE); } void deliver_add_to_cache_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_add_to_cache_data *data = ti->data; xsnprintf(buf, len, "add-to-cache \"%s\" key \"%s\"", data->path, data->key.str); } fdm-1.7+cvs20140912/child-deliver.c0000600000175000017500000001421411206610036015053 0ustar hawkhawk/* $Id: child-deliver.c,v 1.22 2009/05/25 21:39:42 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "fdm.h" #include "match.h" int child_deliver(struct child *, struct io *); int child_deliver(struct child *child, struct io *pio) { struct child_deliver_data *data = child->data; struct account *a = data->account; struct mail *m = data->mail; struct msg msg; struct msgbuf msgbuf; int error = 0; #ifdef DEBUG xmalloc_clear(); COUNTFDS(a->name); #endif log_debug2("%s: deliver started, pid %ld", a->name, (long) getpid()); #ifdef HAVE_SETPROCTITLE setproctitle("%s[%lu]", data->name, (u_long) geteuid()); #endif /* Call the hook. */ memset(&msg, 0, sizeof msg); data->hook(0, a, &msg, data, &msg.data.error); /* Inform parent we're done. */ msg.type = MSG_DONE; msg.id = 0; msgbuf.buf = m->tags; msgbuf.len = STRB_SIZE(m->tags); if (privsep_send(pio, &msg, &msgbuf) != 0) fatalx("privsep_send error"); do { if (privsep_recv(pio, &msg, NULL) != 0) fatalx("privsep_recv error"); } while (msg.type != MSG_EXIT); #ifdef DEBUG COUNTFDS(a->name); xmalloc_report(getpid(), a->name); #endif return (error); } void child_deliver_action_hook(pid_t pid, struct account *a, struct msg *msg, struct child_deliver_data *data, int *result) { struct actitem *ti = data->actitem; struct deliver_ctx *dctx = data->dctx; struct mail *m = data->mail; struct mail *md = &dctx->wr_mail; /* Check if this is the parent. */ if (pid != 0) { /* Use new mail if necessary. */ if (ti->deliver->type != DELIVER_WRBACK) { xfree(dctx); return; } if (*result != DELIVER_SUCCESS) mail_destroy(md); else { mail_close(md); if (mail_receive(m, msg, 0) != 0) { log_warn("parent_deliver: can't receive mail"); *result = DELIVER_FAILURE; } } xfree(dctx); return; } dctx->udata = xmalloc(sizeof *dctx->udata); dctx->udata->uid = data->uid; dctx->udata->gid = data->gid; dctx->udata->name = xstrdup(find_tag(m->tags, "user")); dctx->udata->home = xstrdup(find_tag(m->tags, "home")); log_debug2("%s: deliver user is: %s (%lu/%lu), home is: %s", a->name, dctx->udata->name, (u_long) dctx->udata->uid, (u_long) dctx->udata->gid, dctx->udata->home); /* This is the child. do the delivery. */ *result = ti->deliver->deliver(dctx, ti); if (ti->deliver->type != DELIVER_WRBACK || *result != DELIVER_SUCCESS) { user_free(dctx->udata); return; } user_free(dctx->udata); mail_send(md, msg); log_debug2("%s: using new mail, size %zu", a->name, md->size); } void child_deliver_cmd_hook(pid_t pid, struct account *a, unused struct msg *msg, struct child_deliver_data *data, int *result) { struct mail_ctx *mctx = data->mctx; struct mail *m = data->mail; struct match_command_data *cmddata = data->cmddata; int flags, status, found = 0; char *s, *cause, *lbuf, *out, *err, tag[24]; size_t llen; struct cmd *cmd = NULL; struct rmlist rml; u_int i; /* If this is the parent, do nothing. */ if (pid != 0) { xfree(mctx); return; } /* Sort out the command. */ s = replacepath( &cmddata->cmd, m->tags, m, &m->rml, find_tag(m->tags, "home")); if (s == NULL || *s == '\0') { log_warnx("%s: empty command", a->name); goto error; } log_debug2("%s: %s: started (ret=%d re=%s)", a->name, s, cmddata->ret, cmddata->re.str == NULL ? "none" : cmddata->re.str); flags = CMD_ONCE; if (cmddata->pipe) flags |= CMD_IN; if (cmddata->re.str != NULL) flags |= CMD_OUT; cmd = cmd_start(s, flags, m->data, m->size, &cause); if (cmd == NULL) { log_warnx("%s: %s: %s", a->name, s, cause); goto error; } llen = IO_LINESIZE; lbuf = xmalloc(llen); for (;;) { /* Stop early if looking for regexp only. */ if (found && cmddata->ret == -1) { log_debug3("%s: %s: found. stopping early", a->name, s); status = 1; break; } status = cmd_poll( cmd, &out, &err, &lbuf, &llen, conf.timeout, &cause); if (status == -1) { log_warnx("%s: %s: %s", a->name, s, cause); goto error; } if (status != 0) break; if (err != NULL) log_warnx("%s: %s: %s", a->name, s, err); if (out == NULL) continue; log_debug3("%s: %s: out: %s", a->name, s, out); if (found) continue; found = re_string(&cmddata->re, out, &rml, &cause); if (found == -1) { log_warnx("%s: %s", a->name, cause); goto error; } if (found != 1) continue; /* Save the matches. */ if (!rml.valid) continue; for (i = 0; i < NPMATCH; i++) { if (!rml.list[i].valid) break; xsnprintf(tag, sizeof tag, "command%u", i); add_tag(&m->tags, tag, "%.*s", (int) (rml.list[i].eo - rml.list[i].so), out + rml.list[i].so); } } status--; log_debug2("%s: %s: returned %d, found %d", a->name, s, status, found); cmd_free(cmd); xfree(s); xfree(lbuf); status = cmddata->ret == status; if (cmddata->ret != -1 && cmddata->re.str != NULL) *result = (found && status) ? MATCH_TRUE : MATCH_FALSE; else if (cmddata->ret != -1 && cmddata->re.str == NULL) *result = status ? MATCH_TRUE : MATCH_FALSE; else if (cmddata->ret == -1 && cmddata->re.str != NULL) *result = found ? MATCH_TRUE : MATCH_FALSE; else *result = MATCH_ERROR; return; error: if (cause != NULL) xfree(cause); if (cmd != NULL) cmd_free(cmd); if (s != NULL) xfree(s); if (lbuf != NULL) xfree(lbuf); *result = MATCH_ERROR; } fdm-1.7+cvs20140912/deliver-tag.c0000600000175000017500000000414510647000050014542 0ustar hawkhawk/* $Id: deliver-tag.c,v 1.5 2007/07/16 23:32:56 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_tag_deliver(struct deliver_ctx *, struct actitem *); void deliver_tag_desc(struct actitem *, char *, size_t); struct deliver deliver_tag = { "tag", DELIVER_INCHILD, deliver_tag_deliver, deliver_tag_desc }; int deliver_tag_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_tag_data *data = ti->data; char *tk, *tv; tk = replacestr(&data->key, m->tags, m, &m->rml); if (data->value.str != NULL) tv = replacestr(&data->value, m->tags, m, &m->rml); else tv = xstrdup(""); if (tk == NULL || tv == NULL) { if (tk != NULL) xfree(tk); if (tv != NULL) xfree(tv); return (DELIVER_SUCCESS); } if (*tk != '\0') { log_debug2("%s: tagging message: %s (%s)", a->name, tk, tv); add_tag(&m->tags, tk, "%s", tv); } xfree(tk); xfree(tv); return (DELIVER_SUCCESS); } void deliver_tag_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_tag_data *data = ti->data; if (data->value.str == NULL) xsnprintf(buf, len, "tag \"%s\"", data->key.str); else { xsnprintf(buf, len, "tag \"%s\" value \"%s\"", data->key.str, data->value.str); } } fdm-1.7+cvs20140912/README0000600000175000017500000000135011204062047013052 0ustar hawkhawkfdm is a program designed to fetch mail from POP3 or IMAP servers, or receive local mail from stdin, and deliver it in various ways. As of fdm 0.9, the caching has been changed, so Berkeley DB is no longer required. As of fdm 1.4, TDB is a required dependency. See the included MANUAL file and the fdm(1) and fdm.conf(5) man pages for installation and usage instructions. They are online at: http://fdm.sf.net Some example configurations are included in the examples directory. Feedback, bug reports, suggestions, etc, are welcome. A mailing list is available for fdm users, see: https://lists.sourceforge.net/lists/listinfo/fdm-users Nicholas Marriott nicm@users.sourceforge.net # $Id: README,v 1.45 2009/05/17 19:23:19 nicm Exp $ fdm-1.7+cvs20140912/privsep.c0000600000175000017500000000376611634126723014054 0ustar hawkhawk/* $Id: privsep.c,v 1.13 2011/09/14 13:36:19 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "fdm.h" int privsep_send(struct io *io, struct msg *msg, struct msgbuf *msgbuf) { char *cause; msg->size = 0; if (msgbuf != NULL && msgbuf->buf != NULL && msgbuf->len > 0) msg->size = msgbuf->len; io_write(io, msg, sizeof *msg); if (io_flush(io, INFTIM, &cause) != 0) return (-1); if (msg->size != 0) { io_write(io, msgbuf->buf, msgbuf->len); if (io_flush(io, INFTIM, &cause) != 0) return (-1); } return (0); } int privsep_check(struct io *io) { return (IO_RDSIZE(io) >= sizeof (struct msg)); } int privsep_recv(struct io *io, struct msg *msg, struct msgbuf *msgbuf) { char *tmpbuf; if (msgbuf != NULL) { msgbuf->buf = NULL; msgbuf->len = 0; } if (io_wait(io, sizeof *msg, INFTIM, NULL) != 0) return (-1); if (io_read2(io, msg, sizeof *msg) != 0) return (-1); if (msg->size == 0) return (0); if (io_wait(io, msg->size, INFTIM, NULL) != 0) return (-1); if (msgbuf == NULL) { if ((tmpbuf = io_read(io, msg->size)) == NULL) return (-1); xfree(tmpbuf); } else { if ((msgbuf->buf = io_read(io, msg->size)) == NULL) return (-1); msgbuf->len = msg->size; } return (0); } fdm-1.7+cvs20140912/tools/0000700000175000017500000000000012404656672013351 5ustar hawkhawkfdm-1.7+cvs20140912/tools/makemanual.awk0000600000175000017500000000255511204053061016155 0ustar hawkhawk# $Id: makemanual.awk,v 1.1 2009/05/17 18:23:45 nicm Exp $ # # Copyright (c) 2007 Nicholas Marriott # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # BEGIN { c1 = 0; c2 = 0; h1 = 0; h2 = 0; } /^##[^#]/ { c1++; c2 = 0; print (c1 substr($0, 3)); next; } /^%%[^%]/ { c2++; print (c1 "." c2 substr($0, 3)); next; } /^\*\*\*/ { s = substr($0, 5) " "; while (length(s) < 80) { s = s "="; } print (s); next; } /^###/ { h1++; h2 = 0; s = h1 substr($0, 4) " "; while (length(s) < 80) { s = s "="; } print (s); next; } /^%%%/ { h2++; s = h1 "." h2 substr($0, 4) " "; while (length(s) < 80) { s = s "-"; } print (s); next; } /^.*$/ { print ($0); } fdm-1.7+cvs20140912/tools/makeindex.awk0000600000175000017500000000274311204053061016006 0ustar hawkhawk# $Id: makeindex.awk,v 1.1 2009/05/17 18:23:45 nicm Exp $ # # Copyright (c) 2006 Nicholas Marriott # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # /%%VERSION%%/ { gsub(/%%VERSION%%/, V); print ($0); next; } /^.*$/ { if ($0 ~ /^%%/) { name = substr($0, 3); while ((getline < name) == 1) { print ($0); } close(name); } else if ($0 ~ /^&&/) { name = substr($0, 3); while ((getline < name) == 1) { gsub("\<", "\\<", $0); gsub("\>", "\\>", $0); if ($0 ~ /^[0-9A-Za-z].+ ==+/) { gsub("==+$", "", $0); print ("

" $0 "

"); getline < name; continue; } if ($0 ~ /^[0-9A-Za-z].+ --+/) { gsub("--+$", "", $0); print ("

" $0 "

"); getline < name; continue; } print ($0); } close(name); } else { print ($0); } } fdm-1.7+cvs20140912/tools/dist.mk0000600000175000017500000000205412141465700014634 0ustar hawkhawk# $Id: dist.mk,v 1.3 2013/05/05 14:25:04 nicm Exp $ VERSION= 1.7 DISTDIR= fdm-${VERSION} DISTFILES= *.[chl] Makefile GNUmakefile configure *.[1-9] fdm-sanitize \ README MANUAL TODO CHANGES \ `find examples compat regress -type f -and ! -path '*CVS*'` dist: manual (./configure && make clean-all) grep '^#FDEBUG=' Makefile grep '^#FDEBUG=' GNUmakefile [ "`(grep '^VERSION' Makefile; grep '^VERSION' GNUmakefile)| \ uniq -u`" = "" ] chmod +x configure tar -zc \ -s '/.*/${DISTDIR}\/\0/' \ -f ${DISTDIR}.tar.gz ${DISTFILES} manual: awk -f tools/makemanual.awk MANUAL.in > MANUAL yannotate: awk -f tools/yannotate.awk parse.y > parse.y.new mv parse.y.new parse.y trim parse.y upload-index.html: update-index.html scp index.html nicm,fdm@web.sf.net:/home/groups/f/fd/fdm/htdocs update-index.html: manual mandoc -Thtml fdm.conf.5 > fdm.conf.5.html mandoc -Thtml fdm.1 > fdm.1.html awk -v V=${VERSION} -f tools/makeindex.awk \ index.html.in > index.html rm -f fdm.conf.5.html fdm.1.html fdm-1.7+cvs20140912/tools/yannotate.awk0000600000175000017500000000636411204053061016046 0ustar hawkhawk# $Id: yannotate.awk,v 1.1 2009/05/17 18:23:45 nicm Exp $ # # Copyright (c) 2006 Nicholas Marriott # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # This whole file is pretty crappy and fragile, but it does the job. function convert() { n = 0; delete list; arg = 0; for (i = 2; i <= NF; i++) { arg++; type = rules[$i]; if (type != 0 && types[type] != 0) { list[n] = "[$" arg ": " $i " (" types[type] ")]"; n++; } } return (n); } function pretty(prefix, suffix, n) { s = prefix; column = length(s); for (i = 0; i < n; i++) { if (column + length(list[i]) + length(suffix) + 1 > 79) { column = 0; s = substr(s, 1, length(s) - 1) suffix "\n" prefix; } s = s list[i] " "; column += length(list[i]); } return (substr(s, 1, length(s) - 1) suffix); } function wspace(s, o) { gsub("\t", " ", s); n = match(s, "[^ ]"); n -= o; if (n < 0) n = 0; t = ""; for (i = 0; i < n; i++) { t = t " "; } return (t); } BEGIN { union = 0; name = ""; } /^[ \t]*\/\*\*/ { next; } /^%union/ { print ($0); union = 1; next; } /^%type .+/ { print ($0); if (NF < 3) next; for (i = 3; i <= NF; i++) { rules[$i] = $2; } next; } /^[a-z0-9]+: / { type = rules[substr($1, 1, length($1) - 1)]; if (type != 0) { print ("/** " toupper($1) " " type " (" types[type] ") */"); } else { print ("/** " toupper(substr($1, 1, length($1) - 1)) " */"); } print ($0); elements = convert(); if (elements > 0) { s = "" for (i = 0; i < length($1) - 4; i++) { s = " " s; } print (pretty("/**" s " ", " */", elements)); } next; } /^[ \t]*\| / { print ($0); elements = convert(); if (elements > 0) { s = wspace($0, 4); print (pretty("/**" s " ", " */" s, elements)); } next; } /.*/ { print ($0); if (union == 2) { if (NF == 2 && $1 == "}") { union = 1; name = $NF; if (substr(name, 1, 1) == "*") { type = type " *"; name = substr(name, 2); } name = substr(name, 1, length(name) - 1); types["<" name ">"] = type "... } " name; next; } # Include struct members. #for (i = 1; i <= NF; i++) { # type = type $i " "; #} next; } if (union == 1) { if ($1 == "}") { union = 0; next; } if (NF == 2 && $NF == "{") { type = $(NF - 1) " { "; union = 2; next; } type = "" for (i = 1; i < NF; i++) { type = type $i " "; } type = substr(type, 1, length(type) - 1); name = $NF; if (substr(name, 1, 1) == "*") { type = type " *"; name = substr(name, 2); } name = "<" substr(name, 1, length(name) - 1) ">"; types[name] = type; next; } } fdm-1.7+cvs20140912/lookup-passwd.c0000600000175000017500000000265711204061551015160 0ustar hawkhawk/* $Id: lookup-passwd.c,v 1.2 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" struct userdata * passwd_lookup(const char *user) { struct passwd *pw; struct userdata *ud; uid_t uid; const char *errstr; if ((pw = getpwnam(user)) == NULL) { endpwent(); uid = strtonum(user, 0, UID_MAX, &errstr); if (errstr != NULL) return (NULL); if ((pw = getpwuid(uid)) == NULL) { endpwent(); return (NULL); } } ud = xmalloc(sizeof *ud); ud->name = xstrdup(pw->pw_name); ud->home = xstrdup(pw->pw_dir); ud->uid = pw->pw_uid; ud->gid = pw->pw_gid; endpwent(); return (ud); } fdm-1.7+cvs20140912/child.c0000600000175000017500000000622111233315024013421 0ustar hawkhawk/* $Id: child.c,v 1.150 2009/07/27 12:14:12 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "fdm.h" void child_sighandler(int); void child_sighandler(int sig) { switch (sig) { #ifdef SIGINFO case SIGINFO: #endif case SIGUSR1: sigusr1 = 1; break; case SIGCHLD: break; case SIGTERM: cleanup_purge(); _exit(1); } } int child_fork(void) { pid_t pid; struct sigaction act; switch (pid = fork()) { case -1: fatal("fork failed"); case 0: cleanup_flush(); sigemptyset(&act.sa_mask); #ifdef SIGINFO sigaddset(&act.sa_mask, SIGINFO); #endif sigaddset(&act.sa_mask, SIGUSR1); sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGTERM); sigaddset(&act.sa_mask, SIGCHLD); act.sa_flags = SA_RESTART; act.sa_handler = SIG_IGN; if (sigaction(SIGINT, &act, NULL) < 0) fatal("sigaction failed"); act.sa_handler = child_sighandler; #ifdef SIGINFO if (sigaction(SIGINFO, &act, NULL) < 0) fatal("sigaction failed"); #endif if (sigaction(SIGUSR1, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGTERM, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGCHLD, &act, NULL) < 0) fatal("sigaction failed"); return (0); default: return (pid); } } __dead void child_exit(int status) { cleanup_check(); _exit(status); } struct child * child_start(struct children *children, uid_t uid, gid_t gid, int (*start)(struct child *, struct io *), int (*msg)(struct child *, struct msg *, struct msgbuf *), void *data, struct child *parent) { struct child *child, *childp; int fds[2], n; u_int i; struct io *io; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) != 0) fatal("socketpair failed"); child = xcalloc(1, sizeof *child); child->io = io_create(fds[0], NULL, IO_CRLF); child->data = data; child->msg = msg; child->parent = parent; if ((child->pid = child_fork()) == 0) { for (i = 0; i < ARRAY_LENGTH(children); i++) { childp = ARRAY_ITEM(children, i); if (childp->io != NULL) { io_close(childp->io); io_free(childp->io); } } io_close(child->io); io_free(child->io); if (geteuid() == 0) dropto(uid, gid); io = io_create(fds[1], NULL, IO_LF); n = start(child, io); io_close(io); io_free(io); child_exit(n); } close(fds[1]); ARRAY_ADD(children, child); return (child); } fdm-1.7+cvs20140912/parent-deliver.c0000600000175000017500000000371211206611134015262 0ustar hawkhawk/* $Id: parent-deliver.c,v 1.12 2009/05/25 21:49:16 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" #include "match.h" int parent_deliver(struct child *child, struct msg *msg, struct msgbuf *msgbuf) { struct child_deliver_data *data = child->data; struct account *a = data->account; struct mail *m = data->mail; if (msg->type != MSG_DONE) fatalx("unexpected message"); if (msgbuf->buf == NULL || msgbuf->len == 0) fatalx("bad tags"); strb_destroy(&m->tags); m->tags = msgbuf->buf; /* Call the hook. */ data->hook(1, a, msg, data, &msg->data.error); msg->type = MSG_DONE; msg->id = data->msgid; msgbuf->buf = m->tags; msgbuf->len = STRB_SIZE(m->tags); mail_send(m, msg); /* * Try to send to child. Ignore failures which mean the fetch child * has exited - not much can do about it now. */ child = data->child; if (child->io == NULL || privsep_send(child->io, msg, msgbuf) != 0) log_debug2("%s: child %ld missing", a->name, (long) child->pid); mail_close(m); xfree(m); return (-1); } fdm-1.7+cvs20140912/fetch-nntp.c0000600000175000017500000004167411205502505014420 0ustar hawkhawk/* $Id: fetch-nntp.c,v 1.111 2009/05/22 10:58:13 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" void fetch_nntp_fill(struct account *, struct iolist *); int fetch_nntp_commit(struct account *, struct mail *); void fetch_nntp_abort(struct account *); u_int fetch_nntp_total(struct account *); void fetch_nntp_desc(struct account *, char *, size_t); int fetch_nntp_code(char *); int fetch_nntp_check( struct account *, struct fetch_ctx *, char **, int *, u_int, ...); int fetch_nntp_parse223(char *, u_int *, char **); int fetch_nntp_load(struct account *); int fetch_nntp_save(struct account *); int fetch_nntp_bad(struct account *, const char *); int fetch_nntp_invalid(struct account *, const char *); int fetch_nntp_state_connect(struct account *, struct fetch_ctx *); int fetch_nntp_state_connected(struct account *, struct fetch_ctx *); int fetch_nntp_state_auth1(struct account *, struct fetch_ctx *); int fetch_nntp_state_auth2(struct account *, struct fetch_ctx *); int fetch_nntp_state_switch(struct account *, struct fetch_ctx *); int fetch_nntp_state_group(struct account *, struct fetch_ctx *); int fetch_nntp_state_reset(struct account *, struct fetch_ctx *); int fetch_nntp_state_stat(struct account *, struct fetch_ctx *); int fetch_nntp_state_wait(struct account *, struct fetch_ctx *); int fetch_nntp_state_next(struct account *, struct fetch_ctx *); int fetch_nntp_state_article(struct account *, struct fetch_ctx *); int fetch_nntp_state_line(struct account *, struct fetch_ctx *); int fetch_nntp_state_quit(struct account *, struct fetch_ctx *); struct fetch fetch_nntp = { "nntp", fetch_nntp_state_connect, fetch_nntp_fill, fetch_nntp_commit, fetch_nntp_abort, NULL, fetch_nntp_desc }; int fetch_nntp_bad(struct account *a, const char *line) { log_warnx("%s: unexpected data: %s", a->name, line); return (FETCH_ERROR); } int fetch_nntp_invalid(struct account *a, const char *line) { log_warnx("%s: invalid response: %s", a->name, line); return (FETCH_ERROR); } /* Extract code from line. */ int fetch_nntp_code(char *line) { char ch; const char *errstr; int n; size_t len; len = strspn(line, "0123456789"); if (len == 0) return (-1); ch = line[len]; line[len] = '\0'; n = strtonum(line, 100, 999, &errstr); line[len] = ch; if (errstr != NULL) return (-1); return (n); } /* * Get line from server and check against list of codes. Returns -1 on error, * 0 on success, a NULL line when out of data. */ int fetch_nntp_check(struct account *a, struct fetch_ctx *fctx, char **line, int *codep, u_int n, ...) { struct fetch_nntp_data *data = a->data; va_list ap; u_int i; int code; if (codep == NULL) codep = &code; do { *line = io_readline2(data->io, &fctx->lbuf, &fctx->llen); if (*line == NULL) return (0); *codep = fetch_nntp_code(*line); if (*codep == -1) goto error; } while (*codep >= 100 && *codep <= 199); va_start(ap, n); for (i = n; i > 0; i--) { if (*codep == va_arg(ap, int)) break; } va_end(ap); if (i == 0) goto error; return (0); error: log_warnx("%s: unexpected data: %s", a->name, *line); return (-1); } /* Extract id from 223 code. */ int fetch_nntp_parse223(char *line, u_int *n, char **id) { char *ptr, *ptr2; if (sscanf(line, "223 %u ", n) != 1) return (-1); ptr = strchr(line, '<'); if (ptr == NULL) return (-1); ptr2 = strchr(ptr, '>'); if (ptr2 == NULL) return (-1); ptr++; *id = xmalloc(ptr2 - ptr + 1); memcpy(*id, ptr, ptr2 - ptr); (*id)[ptr2 - ptr] = '\0'; return (0); } /* Load NNTP cache file. */ int fetch_nntp_load(struct account *a) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; int fd; FILE *f; char *name, *id; size_t namelen, idlen; u_int last, i; f = NULL; if ((fd = openlock(data->path, O_RDONLY, conf.lock_types)) == -1) { if (errno == ENOENT) return (0); log_warn("%s: %s", a->name, data->path); goto error; } if ((f = fdopen(fd, "r")) == NULL) { log_warn("%s: %s", a->name, data->path); goto error; } for (;;) { if (fscanf(f, "%zu ", &namelen) != 1) { /* EOF is allowed only at the start of a line. */ if (feof(f)) break; goto invalid; } name = xmalloc(namelen + 1); if (fread(name, namelen, 1, f) != 1) goto invalid; name[namelen] = '\0'; if (fscanf(f, " %u ", &last) != 1) goto invalid; if (fscanf(f, "%zu ", &idlen) != 1) goto invalid; id = xmalloc(idlen + 1); if (fread(id, idlen, 1, f) != 1) goto invalid; id[idlen] = '\0'; /* Got a group. Fill it in. */ group = NULL; for (i = 0; i < ARRAY_LENGTH(&data->groups); i++) { group = ARRAY_ITEM(&data->groups, i); if (strcmp(group->name, name) == 0) break; } if (i == ARRAY_LENGTH(&data->groups)) { /* * Not found. add it so it is saved when the file is * resaved, but with ignore set so it isn't fetched. */ group = xcalloc(1, sizeof *group); ARRAY_ADD(&data->groups, group); group->ignore = 1; group->name = xstrdup(name); } log_debug2("%s: found group in cache: %s", a->name, name); group->last = last; group->id = id; xfree(name); } fclose(f); closelock(fd, data->path, conf.lock_types); return (0); invalid: log_warnx("%s: invalid cache entry", a->name); error: if (f != NULL) fclose(f); if (fd != -1) closelock(fd, data->path, conf.lock_types); return (-1); } /* Save NNTP cache file. */ int fetch_nntp_save(struct account *a) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; char *path = NULL, tmp[MAXPATHLEN]; int fd = -1; FILE *f = NULL; u_int i; if (ppath(tmp, sizeof tmp, "%s.XXXXXXXXXX", data->path) != 0) goto error; if ((fd = mkstemp(tmp)) == -1) goto error; path = tmp; cleanup_register(path); if ((f = fdopen(fd, "r+")) == NULL) goto error; fd = -1; for (i = 0; i < ARRAY_LENGTH(&data->groups); i++) { group = ARRAY_ITEM(&data->groups, i); if (group->id == NULL) continue; fprintf(f, "%zu %s %u %zu %s\n", strlen(group->name), group->name, group->last, strlen(group->id), group->id); } if (fflush(f) != 0) goto error; if (fsync(fileno(f)) != 0) goto error; fclose(f); f = NULL; if (rename(path, data->path) == -1) goto error; cleanup_deregister(path); return (0); error: log_warn("%s: %s", a->name, data->path); if (f != NULL) fclose(f); if (fd != -1) close(fd); if (path != NULL) { if (unlink(tmp) != 0) fatal("unlink failed"); cleanup_deregister(path); } return (-1); } /* Fill io list. */ void fetch_nntp_fill(struct account *a, struct iolist *iol) { struct fetch_nntp_data *data = a->data; if (data->io != NULL) ARRAY_ADD(iol, data->io); } /* Commit mail. We just do a save here. */ int fetch_nntp_commit(struct account *a, unused struct mail *m) { if (fetch_nntp_save(a) != 0) return (FETCH_ERROR); return (FETCH_AGAIN); } /* Abort fetch and free everything. */ void fetch_nntp_abort(struct account *a) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; u_int i; if (data->io != NULL) { io_close(data->io); io_free(data->io); data->io = NULL; } for (i = 0; i < ARRAY_LENGTH(&data->groups); i++) { group = ARRAY_ITEM(&data->groups, i); xfree(group->name); if (group->id != NULL) xfree(group->id); xfree(group); } ARRAY_FREE(&data->groups); } /* Connect to NNTP server. */ int fetch_nntp_state_connect(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; u_int i; char *cause; /* Initialise and load groups array. */ ARRAY_INIT(&data->groups); for (i = 0; i < ARRAY_LENGTH(data->names); i++) { group = xcalloc(1, sizeof *group); group->name = xstrdup(ARRAY_ITEM(data->names, i)); group->id = NULL; group->ignore = 0; ARRAY_ADD(&data->groups, group); } if (fetch_nntp_load(a) != 0) return (FETCH_ERROR); /* Find the first active group, if any. */ data->group = 0; while (ARRAY_ITEM(&data->groups, data->group)->ignore) { data->group++; if (data->group == ARRAY_LENGTH(&data->groups)) { log_warnx("%s: no groups found", a->name); return (FETCH_ERROR); } } /* Connect to the server. */ data->io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (data->io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (FETCH_ERROR); } if (conf.debug > 3 && !conf.syslog) data->io->dup_fd = STDOUT_FILENO; fctx->state = fetch_nntp_state_connected; return (FETCH_BLOCK); } /* Connected state. */ int fetch_nntp_state_connected(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; char *line; if (fetch_nntp_check(a, fctx, &line, NULL, 2, 200, 201) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (data->user == NULL || data->pass == NULL) { fctx->state = fetch_nntp_state_group; return (FETCH_AGAIN); } io_writeline(data->io, "AUTHINFO USER %s", data->user); fctx->state = fetch_nntp_state_auth1; return (FETCH_BLOCK); } /* First authentication state. */ int fetch_nntp_state_auth1(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; char *line; if (fetch_nntp_check(a, fctx, &line, NULL, 1, 381) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); io_writeline(data->io, "AUTHINFO PASS %s", data->pass); fctx->state = fetch_nntp_state_auth2; return (FETCH_BLOCK); } /* Second authentication state. */ int fetch_nntp_state_auth2(struct account *a, struct fetch_ctx *fctx) { char *line; if (fetch_nntp_check(a, fctx, &line, NULL, 2, 281) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); fctx->state = fetch_nntp_state_group; return (FETCH_AGAIN); } /* * Switch to the next group. Missed out the first time since connect already * finds the first group. */ int fetch_nntp_state_switch(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; /* Find the next group. */ do { data->group++; if (data->group == ARRAY_LENGTH(&data->groups)) { io_writeline(data->io, "QUIT"); fctx->state = fetch_nntp_state_quit; return (FETCH_BLOCK); } } while (ARRAY_ITEM(&data->groups, data->group)->ignore); fctx->state = fetch_nntp_state_group; return (FETCH_AGAIN); } /* Group state. */ int fetch_nntp_state_group(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; group = ARRAY_ITEM(&data->groups, data->group); log_debug("%s: fetching group: %s", a->name, group->name); io_writeline(data->io, "GROUP %s", group->name); fctx->state = fetch_nntp_state_stat; return (FETCH_BLOCK); } /* Reset state. */ int fetch_nntp_state_reset(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; group = ARRAY_ITEM(&data->groups, data->group); log_warnx("%s: last message not found. resetting group", a->name); if (group->id != NULL) { xfree(group->id); group->id = NULL; } group->last = 0; fctx->state = fetch_nntp_state_group; return (FETCH_AGAIN); } /* Stat state. */ int fetch_nntp_state_stat(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; char *line; u_int n; group = ARRAY_ITEM(&data->groups, data->group); if (fetch_nntp_check(a, fctx, &line, NULL, 1, 211) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (sscanf(line, "211 %u %*u %u", &group->size, &n) != 2) return (fetch_nntp_invalid(a, line)); if (group->last > n) { log_warnx("%s: " "new last %u is less than old %u", a->name, n, group->last); fctx->state = fetch_nntp_state_reset; return (FETCH_AGAIN); } group->size = n - group->last; if (group->last != 0) { io_writeline(data->io, "STAT %u", group->last); fctx->state = fetch_nntp_state_wait; return (FETCH_BLOCK); } else { io_writeline(data->io, "NEXT"); fctx->state = fetch_nntp_state_next; return (FETCH_BLOCK); } } /* Wait state. Wait for and check STAT response. */ int fetch_nntp_state_wait(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; char *line, *id; u_int n; int code; group = ARRAY_ITEM(&data->groups, data->group); if (fetch_nntp_check(a, fctx, &line, &code, 2, 223, 423) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (code == 223) { if (fetch_nntp_parse223(line, &n, &id) != 0) return (fetch_nntp_invalid(a, line)); if (n != group->last) { log_warnx("%s: unexpected message number", a->name); xfree(id); return (FETCH_ERROR); } if (strcmp(id, group->id) != 0) { xfree(id); fctx->state = fetch_nntp_state_reset; return (FETCH_AGAIN); } log_debug2( "%s: last message found: %u %s", a->name, group->last, id); xfree(id); } else log_warnx("%s: could not get last message", a->name); io_writeline(data->io, "NEXT"); fctx->state = fetch_nntp_state_next; return (FETCH_BLOCK); } /* Next state. Now we are fetching mail. */ int fetch_nntp_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; char *line, *id; u_int n; int code; group = ARRAY_ITEM(&data->groups, data->group); if (fetch_nntp_check(a, fctx, &line, &code, 3, 223, 420, 421) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (code == 420 || code == 421) { /* Finished this group. Switch to the next. */ fctx->state = fetch_nntp_state_switch; return (FETCH_AGAIN); } /* 223 code. Save this as last article. */ if (fetch_nntp_parse223(line, &n, &id) != 0) return (fetch_nntp_invalid(a, line)); group->last = n; if (group->id != NULL) xfree(group->id); group->id = id; io_writeline(data->io, "ARTICLE"); fctx->state = fetch_nntp_state_article; return (FETCH_BLOCK); } /* Article state. */ int fetch_nntp_state_article(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct fetch_nntp_group *group; struct mail *m = fctx->mail; char *line; int code; group = ARRAY_ITEM(&data->groups, data->group); if (fetch_nntp_check(a, fctx, &line, &code, 3, 220, 423, 430) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); if (code == 423 || code == 430) { io_writeline(data->io, "NEXT"); fctx->state = fetch_nntp_state_next; return (FETCH_BLOCK); } /* Open the mail. */ if (mail_open(m, IO_BLOCKSIZE) != 0) { log_warn("%s: failed to create mail", a->name); return (FETCH_ERROR); } m->size = 0; /* Tag mail. */ default_tags(&m->tags, group->name); add_tag(&m->tags, "group", "%s", group->name); add_tag(&m->tags, "server", "%s", data->server.host); add_tag(&m->tags, "port", "%s", data->server.port); data->flushing = 0; fctx->state = fetch_nntp_state_line; return (FETCH_AGAIN); } /* Line state. */ int fetch_nntp_state_line(struct account *a, struct fetch_ctx *fctx) { struct fetch_nntp_data *data = a->data; struct mail *m = fctx->mail; char *line; for (;;) { line = io_readline2(data->io, &fctx->lbuf, &fctx->llen); if (line == NULL) return (FETCH_BLOCK); if (line[0] == '.') { if (line[1] == '\0') break; line++; } if (data->flushing) continue; if (append_line(m, line, strlen(line)) != 0) { log_warn("%s: failed to resize mail", a->name); return (FETCH_ERROR); } if (m->size > conf.max_size) data->flushing = 1; } io_writeline(data->io, "NEXT"); fctx->state = fetch_nntp_state_next; return (FETCH_MAIL); } /* Quit state. */ int fetch_nntp_state_quit(struct account *a, struct fetch_ctx *fctx) { char *line; if (fetch_nntp_check(a, fctx, &line, NULL, 1, 205) != 0) return (FETCH_ERROR); if (line == NULL) return (FETCH_BLOCK); fetch_nntp_abort(a); return (FETCH_EXIT); } void fetch_nntp_desc(struct account *a, char *buf, size_t len) { struct fetch_nntp_data *data = a->data; char *names; names = fmt_strings("groups ", data->names); xsnprintf(buf, len, "nntp server \"%s\" port %s %s cache \"%s\"", data->server.host, data->server.port, names, data->path); xfree(names); } fdm-1.7+cvs20140912/fetch-pop3.c0000600000175000017500000000650610674032156014326 0ustar hawkhawk/* $Id: fetch-pop3.c,v 1.113 2007/09/18 20:26:22 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "fetch.h" void fetch_pop3_fill(struct account *, struct iolist *); void fetch_pop3_desc(struct account *, char *, size_t); int fetch_pop3_connect(struct account *); void fetch_pop3_disconnect(struct account *); int fetch_pop3_putln(struct account *, const char *, va_list); int fetch_pop3_getln(struct account *, struct fetch_ctx *, char **); int fetch_pop3_state_init(struct account *, struct fetch_ctx *); struct fetch fetch_pop3 = { "pop3", fetch_pop3_state_init, fetch_pop3_fill, pop3_commit, /* from pop3-common.c */ pop3_abort, /* from pop3-common.c */ pop3_total, /* from pop3-common.c */ fetch_pop3_desc }; /* Write line to server. */ int fetch_pop3_putln(struct account *a, const char *fmt, va_list ap) { struct fetch_pop3_data *data = a->data; io_vwriteline(data->io, fmt, ap); return (0); } /* Get line from server. */ int fetch_pop3_getln(struct account *a, struct fetch_ctx *fctx, char **line) { struct fetch_pop3_data *data = a->data; *line = io_readline2(data->io, &fctx->lbuf, &fctx->llen); return (0); } /* Fill io list. */ void fetch_pop3_fill(struct account *a, struct iolist *iol) { struct fetch_pop3_data *data = a->data; ARRAY_ADD(iol, data->io); } /* Connect to server. */ int fetch_pop3_connect(struct account *a) { struct fetch_pop3_data *data = a->data; char *cause; data->io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (data->io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (-1); } if (conf.debug > 3 && !conf.syslog) data->io->dup_fd = STDOUT_FILENO; return (0); } /* Close connection. */ void fetch_pop3_disconnect(struct account *a) { struct fetch_pop3_data *data = a->data; if (data->io != NULL) { io_close(data->io); io_free(data->io); data->io = NULL; } } /* Initial POP3 state. */ int fetch_pop3_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_pop3_data *data = a->data; data->connect = fetch_pop3_connect; data->getln = fetch_pop3_getln; data->putln = fetch_pop3_putln; data->disconnect = fetch_pop3_disconnect; data->src = data->server.host; return (pop3_state_init(a, fctx)); } void fetch_pop3_desc(struct account *a, char *buf, size_t len) { struct fetch_pop3_data *data = a->data; xsnprintf(buf, len, "pop3%s server \"%s\" port %s user \"%s\"", data->server.ssl ? "s" : "", data->server.host, data->server.port, data->user); } fdm-1.7+cvs20140912/fetch-imap.c0000600000175000017500000000634611015361417014370 0ustar hawkhawk/* $Id: fetch-imap.c,v 1.82 2008/05/22 21:18:07 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "fetch.h" void fetch_imap_fill(struct account *, struct iolist *); void fetch_imap_desc(struct account *, char *, size_t); int fetch_imap_connect(struct account *); void fetch_imap_disconnect(struct account *); struct fetch fetch_imap = { "imap", fetch_imap_state_init, fetch_imap_fill, imap_commit, /* from imap-common.c */ imap_abort, /* from imap-common.c */ imap_total, /* from imap-common.c */ fetch_imap_desc }; /* Write line to server. */ int fetch_imap_putln(struct account *a, const char *fmt, va_list ap) { struct fetch_imap_data *data = a->data; io_vwriteline(data->io, fmt, ap); return (0); } /* Get line from server. */ int fetch_imap_getln(struct account *a, struct fetch_ctx *fctx, char **line) { struct fetch_imap_data *data = a->data; *line = io_readline2(data->io, &fctx->lbuf, &fctx->llen); return (0); } /* Fill io list. */ void fetch_imap_fill(struct account *a, struct iolist *iol) { struct fetch_imap_data *data = a->data; ARRAY_ADD(iol, data->io); } /* Connect to server. */ int fetch_imap_connect(struct account *a) { struct fetch_imap_data *data = a->data; char *cause; data->io = connectproxy(&data->server, conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause); if (data->io == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (-1); } if (conf.debug > 3 && !conf.syslog) data->io->dup_fd = STDOUT_FILENO; return (0); } /* Close connection. */ void fetch_imap_disconnect(struct account *a) { struct fetch_imap_data *data = a->data; if (data->io != NULL) { io_close(data->io); io_free(data->io); data->io = NULL; } } /* IMAP initial state. */ int fetch_imap_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; data->connect = fetch_imap_connect; data->getln = fetch_imap_getln; data->putln = fetch_imap_putln; data->disconnect = fetch_imap_disconnect; data->src = data->server.host; return (imap_state_init(a, fctx)); } void fetch_imap_desc(struct account *a, char *buf, size_t len) { struct fetch_imap_data *data = a->data; char *folders; folders = fmt_strings("folders ", data->folders); xsnprintf(buf, len, "imap%s server \"%s\" port %s user \"%s\" %s", data->server.ssl ? "s" : "", data->server.host, data->server.port, data->user, folders); xfree(folders); } fdm-1.7+cvs20140912/fdm.c0000600000175000017500000005453312277447207013137 0ustar hawkhawk/* $Id: fdm.c,v 1.186 2014/02/14 17:12:39 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #if defined(__OpenBSD__) && defined(DEBUG) const char *malloc_options = "AFGJPRX"; #endif void sighandler(int); struct child *check_children(struct children *, u_int *); int wait_children( struct children *, struct children *, int); struct conf conf; volatile sig_atomic_t sigchld; volatile sig_atomic_t sigusr1; volatile sig_atomic_t sigint; volatile sig_atomic_t sigterm; void sighandler(int sig) { switch (sig) { #ifdef SIGINFO case SIGINFO: #endif case SIGUSR1: sigusr1 = 1; break; case SIGINT: sigint = 1; break; case SIGTERM: sigterm = 1; break; case SIGCHLD: sigchld = 1; break; } } double get_time(void) { struct timeval tv; if (gettimeofday(&tv, NULL) != 0) fatal("gettimeofday failed"); return (tv.tv_sec + tv.tv_usec / 1000000.0); } void fill_host(void) { char host[MAXHOSTNAMELEN]; if (gethostname(host, sizeof host) != 0) fatal("gethostname failed"); conf.host_name = xstrdup(host); getaddrs(host, &conf.host_fqdn, &conf.host_address); } void dropto(uid_t uid, gid_t gid) { if (uid == (uid_t) -1 || uid == 0) return; if (gid == (gid_t) -1 || gid == 0) return; if (setgroups(1, &gid) != 0) fatal("setgroups failed"); if (setresgid(gid, gid, gid) != 0) fatal("setresgid failed"); if (setresuid(uid, uid, uid) != 0) fatal("setresuid failed"); } int check_incl(const char *name) { u_int i; if (ARRAY_EMPTY(&conf.incl)) return (1); for (i = 0; i < ARRAY_LENGTH(&conf.incl); i++) { if (account_match(ARRAY_ITEM(&conf.incl, i), name)) return (1); } return (0); } int check_excl(const char *name) { u_int i; if (ARRAY_EMPTY(&conf.excl)) return (0); for (i = 0; i < ARRAY_LENGTH(&conf.excl); i++) { if (account_match(ARRAY_ITEM(&conf.excl, i), name)) return (1); } return (0); } int use_account(struct account *a, char **cause) { if (!check_incl(a->name)) { if (cause != NULL) xasprintf(cause, "account %s is not included", a->name); return (0); } if (check_excl(a->name)) { if (cause != NULL) xasprintf(cause, "account %s is excluded", a->name); return (0); } /* * If the account is disabled and no accounts are specified on the * command line (whether or not it is included if there are is already * confirmed), then skip it. */ if (a->disabled && ARRAY_EMPTY(&conf.incl)) { if (cause != NULL) xasprintf(cause, "account %s is disabled", a->name); return (0); } return (1); } /* Check each child for a privsep message. */ struct child * check_children(struct children *children, u_int *idx) { struct child *child; for (*idx = 0; *idx < ARRAY_LENGTH(children); (*idx)++) { child = ARRAY_ITEM(children, *idx); if (child->io != NULL && privsep_check(child->io)) return (child); } return (NULL); } /* Wait for a child and deal with its exit. */ int wait_children( struct children *children, struct children *dead_children, int no_hang) { struct child *child, *child2; pid_t pid; int status, flags, retcode = 0; u_int i, j; flags = no_hang ? WNOHANG : 0; for (;;) { log_debug3("parent: waiting for children"); /* Wait for a child. */ switch (pid = waitpid(WAIT_ANY, &status, flags)) { case 0: return (0); case -1: if (errno == ECHILD) return (0); fatal("waitpid failed"); } /* Handle the exit status. */ if (WIFSIGNALED(status)) { retcode = 1; log_debug2("parent: child %ld got signal %d", (long) pid, WTERMSIG(status)); } else if (!WIFEXITED(status)) { retcode = 1; log_debug2("parent: child %ld exited badly", (long) pid); } else { if (WEXITSTATUS(status) != 0) retcode = 1; log_debug2("parent: child %ld returned %d", (long) pid, WEXITSTATUS(status)); } /* Find this child. */ child = NULL; for (i = 0; i < ARRAY_LENGTH(children); i++) { child = ARRAY_ITEM(children, i); if (pid == child->pid) break; } if (i == ARRAY_LENGTH(children)) { log_debug2("parent: unidentified child %ld", (long) pid); continue; } if (child->io != NULL) { io_close(child->io); io_free(child->io); child->io = NULL; } ARRAY_REMOVE(children, i); ARRAY_ADD(dead_children, child); /* If this child was the parent of any others, kill them too. */ for (j = 0; j < ARRAY_LENGTH(children); j++) { child2 = ARRAY_ITEM(children, j); if (child2->parent != child) continue; log_debug2("parent: child %ld died: killing %ld", (long) child->pid, (long) child2->pid); kill(child2->pid, SIGTERM); } } return (retcode); } __dead void usage(void) { fprintf(stderr, "usage: %s [-hklmnqv] [-a name] [-D name=value] [-f conffile] " "[-u user] [-x name] [fetch|poll|cache] [arguments]\n", __progname); exit(1); } int main(int argc, char **argv) { int opt, lockfd, status, res; u_int i; enum fdmop op = FDMOP_NONE; const char *proxy = NULL, *s; char tmp[BUFSIZ], *ptr, *lock = NULL, *user, *home = NULL; struct utsname un; struct passwd *pw; struct stat sb; time_t tt; struct account *a; TAILQ_HEAD(, account) actaq; /* active accounts */ pid_t pid; struct children children, dead_children; struct child *child; struct io *dead_io; struct iolist iol; double tim; struct sigaction act; struct msg msg; struct msgbuf msgbuf; size_t off; struct strings macros; struct child_fetch_data *cfd; struct userdata *ud; #ifdef DEBUG struct rule *r; struct action *t; struct cache *cache; #endif log_open_tty(0); memset(&conf, 0, sizeof conf); TAILQ_INIT(&conf.accounts); TAILQ_INIT(&conf.rules); TAILQ_INIT(&conf.actions); TAILQ_INIT(&conf.caches); conf.max_size = DEFMAILSIZE; conf.timeout = DEFTIMEOUT; conf.lock_types = LOCK_FLOCK; conf.impl_act = DECISION_NONE; conf.purge_after = 0; conf.file_umask = DEFUMASK; conf.file_group = -1; conf.queue_high = -1; conf.queue_low = -1; conf.def_user = NULL; conf.cmd_user = NULL; conf.max_accts = -1; conf.strip_chars = xstrdup(DEFSTRIPCHARS); conf.user_order = xmalloc(sizeof *conf.user_order); ARRAY_INIT(conf.user_order); ARRAY_ADD(conf.user_order, passwd_lookup); ARRAY_INIT(&conf.incl); ARRAY_INIT(&conf.excl); ARRAY_INIT(¯os); while ((opt = getopt(argc, argv, "a:D:f:hklmnqu:vx:")) != -1) { switch (opt) { case 'a': ARRAY_ADD(&conf.incl, xstrdup(optarg)); break; case 'D': ARRAY_ADD(¯os, optarg); break; case 'f': if (conf.conf_file == NULL) conf.conf_file = xstrdup(optarg); break; case 'h': home = getenv("HOME"); break; case 'k': conf.keep_all = 1; break; case 'l': conf.syslog = 1; break; case 'm': conf.allow_many = 1; break; case 'n': conf.check_only = 1; break; case 'u': if (conf.def_user == NULL) conf.def_user = xstrdup(optarg); break; case 'v': if (conf.debug != -1) conf.debug++; break; case 'q': conf.debug = -1; break; case 'x': ARRAY_ADD(&conf.excl, xstrdup(optarg)); break; default: usage(); } } argc -= optind; argv += optind; if (conf.check_only) { if (argc != 0) usage(); } else { if (argc < 1) usage(); if (strncmp(argv[0], "poll", strlen(argv[0])) == 0) { if (argc != 1) usage(); op = FDMOP_POLL; } else if (strncmp(argv[0], "fetch", strlen(argv[0])) == 0) { if (argc != 1) usage(); op = FDMOP_FETCH; } else if (strncmp(argv[0], "cache", strlen(argv[0])) == 0) op = FDMOP_CACHE; else usage(); } /* Set debug level and start logging to syslog if necessary. */ if (conf.syslog) log_open_syslog(conf.debug); else log_open_tty(conf.debug); tt = time(NULL); log_debug("version is: %s " BUILD ", started at: %.24s", __progname, ctime(&tt)); /* And the OS version. */ if (uname(&un) == 0) { log_debug2("running on: %s %s %s %s", un.sysname, un.release, un.version, un.machine); } else log_debug2("uname: %s", strerror(errno)); /* Fill the hostname. */ fill_host(); log_debug2("host is: %s %s %s", conf.host_name, conf.host_fqdn, conf.host_address); /* Find invoking user's details. */ if ((pw = getpwuid(getuid())) == NULL) { log_warnx("unknown user: %lu", (u_long) geteuid()); exit(1); } user = xstrdup(pw->pw_name); if (home != NULL && *home != '\0') conf.user_home = xstrdup(home); else conf.user_home = xstrdup(pw->pw_dir); log_debug2("home is: %s", conf.user_home); endpwent(); /* Find the config file. */ if (conf.conf_file == NULL) { /* If no file specified, try ~ then /etc. */ xasprintf(&conf.conf_file, "%s/%s", conf.user_home, CONFFILE); if (access(conf.conf_file, R_OK) != 0) { xfree(conf.conf_file); conf.conf_file = xstrdup(SYSCONFFILE); } } log_debug2("loading configuration from %s", conf.conf_file); if (stat(conf.conf_file, &sb) == -1) { log_warn("%s", conf.conf_file); exit(1); } if (geteuid() != 0 && (sb.st_mode & (S_IROTH|S_IWOTH)) != 0) log_warnx("%s: world readable or writable", conf.conf_file); if (parse_conf(conf.conf_file, ¯os) != 0) { log_warn("%s", conf.conf_file); exit(1); } ARRAY_FREE(¯os); log_debug2("configuration loaded"); /* Fill in users if not set already in configuration file. */ if (conf.def_user == NULL) conf.def_user = xstrdup(user); if (conf.cmd_user == NULL) conf.cmd_user = xstrdup(user); xfree(user); /* Sort out queue limits. */ if (conf.queue_high == -1) conf.queue_high = DEFMAILQUEUE; if (conf.queue_low == -1) { conf.queue_low = conf.queue_high * 3 / 4; if (conf.queue_low >= conf.queue_high) conf.queue_low = conf.queue_high - 1; } /* Set the umask. */ umask(conf.file_umask); /* Check default and command users. */ if (conf.def_user == NULL) { ud = user_lookup(conf.def_user, conf.user_order); if (ud == NULL) { log_warnx("unknown user: %s", conf.def_user); exit(1); } user_free(ud); } if (conf.cmd_user == NULL) { ud = user_lookup(conf.cmd_user, conf.user_order); if (ud == NULL) { log_warnx("unknown user: %s", conf.cmd_user); exit(1); } user_free(ud); } /* Print proxy info. */ if (conf.proxy != NULL) { switch (conf.proxy->type) { case PROXY_HTTP: proxy = "HTTP"; break; case PROXY_HTTPS: proxy = "HTTPS"; break; case PROXY_SOCKS5: proxy = "SOCKS5"; break; } log_debug2("using proxy: %s on %s:%s", proxy, conf.proxy->server.host, conf.proxy->server.port); } /* Print some locking info. */ *tmp = '\0'; if (conf.lock_types == 0) strlcpy(tmp, "none", sizeof tmp); else { if (conf.lock_types & LOCK_FCNTL) strlcat(tmp, "fcntl ", sizeof tmp); if (conf.lock_types & LOCK_FLOCK) strlcat(tmp, "flock ", sizeof tmp); if (conf.lock_types & LOCK_DOTLOCK) strlcat(tmp, "dotlock ", sizeof tmp); } log_debug2("locking using: %s", tmp); /* Print the other settings. */ *tmp = '\0'; off = 0; if (conf.allow_many) off = strlcat(tmp, "allow-multiple, ", sizeof tmp); if (conf.no_received) off = strlcat(tmp, "no-received, ", sizeof tmp); if (conf.keep_all) off = strlcat(tmp, "keep-all, ", sizeof tmp); if (conf.del_big) off = strlcat(tmp, "delete-oversized, ", sizeof tmp); if (conf.verify_certs) off = strlcat(tmp, "verify-certificates, ", sizeof tmp); if (sizeof tmp > off && conf.purge_after > 0) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "purge-after=%u, ", conf.purge_after); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "maximum-size=%zu, ", conf.max_size); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "timeout=%d, ", conf.timeout / 1000); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "default-user=\"%s\", ", conf.def_user); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "command-user=\"%s\", ", conf.cmd_user); } if (sizeof tmp > off && conf.impl_act != DECISION_NONE) { if (conf.impl_act == DECISION_DROP) s = "drop"; else if (conf.impl_act == DECISION_KEEP) s = "keep"; else s = "none"; off += xsnprintf(tmp + off, (sizeof tmp) - off, "unmatched-mail=%s, ", s); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "file-umask=%o%o%o, ", MODE(conf.file_umask)); } if (sizeof tmp > off && conf.file_group != (gid_t) -1) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "file-group=%lu, ", (u_long) conf.file_group); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "queue-high=%u, queue-low=%u, ", conf.queue_high, conf.queue_low); } if (sizeof tmp > off && conf.max_accts != -1) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "parallel-accounts=%d, ", conf.max_accts); } if (sizeof tmp > off && conf.lock_file != NULL) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "lock-file=\"%s\", ", conf.lock_file); } if (sizeof tmp > off) { off += xsnprintf(tmp + off, (sizeof tmp) - off, "strip-characters=\"%s\", ", conf.strip_chars); } if (off >= 2) { tmp[off - 2] = '\0'; log_debug2("options are: %s", tmp); } /* Save and print tmp dir. */ s = getenv("TMPDIR"); if (s == NULL || *s == '\0') s = _PATH_TMP; else { if (stat(s, &sb) == -1 || !S_ISDIR(sb.st_mode)) { log_warn("%s", s); s = _PATH_TMP; } } conf.tmp_dir = xstrdup(s); while ((ptr = strrchr(conf.tmp_dir, '/')) != NULL) { if (ptr == conf.tmp_dir || ptr[1] != '\0') break; *ptr = '\0'; } log_debug2("using tmp directory: %s", conf.tmp_dir); /* If -n, bail now, otherwise check there is something to work with. */ if (conf.check_only) exit(0); if (TAILQ_EMPTY(&conf.accounts)) { log_warnx("no accounts specified"); exit(1); } if (op == FDMOP_FETCH && TAILQ_EMPTY(&conf.rules)) { log_warnx("no rules specified"); exit(1); } /* Change to handle cache ops. */ if (op == FDMOP_CACHE) { argc--; argv++; cache_op(argc, argv); } /* Check for child user if root. */ if (geteuid() == 0) { pw = getpwnam(CHILDUSER); if (pw == NULL) { log_warnx("can't find user: %s", CHILDUSER); exit(1); } conf.child_uid = pw->pw_uid; conf.child_gid = pw->pw_gid; endpwent(); } /* Set up signal handlers. */ memset(&act, 0, sizeof act); sigemptyset(&act.sa_mask); #ifdef SIGINFO sigaddset(&act.sa_mask, SIGINFO); #endif sigaddset(&act.sa_mask, SIGUSR1); sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGTERM); sigaddset(&act.sa_mask, SIGCHLD); act.sa_flags = SA_RESTART; act.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGUSR2, &act, NULL) < 0) fatal("sigaction failed"); act.sa_handler = sighandler; #ifdef SIGINFO if (sigaction(SIGINFO, &act, NULL) < 0) fatal("sigaction failed"); #endif if (sigaction(SIGUSR1, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGINT, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGTERM, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGCHLD, &act, NULL) < 0) fatal("sigaction failed"); /* Check lock file. */ lock = conf.lock_file; if (lock == NULL) { if (geteuid() == 0) lock = xstrdup(SYSLOCKFILE); else xasprintf(&lock, "%s/%s", conf.user_home, LOCKFILE); } if (*lock != '\0' && !conf.allow_many) { lockfd = xcreate(lock, O_WRONLY, -1, -1, S_IRUSR|S_IWUSR); if (lockfd == -1 && errno == EEXIST) { log_warnx("already running (%s exists)", lock); exit(1); } else if (lockfd == -1) { log_warn("%s: open", lock); exit(1); } close(lockfd); } conf.lock_file = lock; SSL_library_init(); SSL_load_error_strings(); #ifdef DEBUG COUNTFDS("parent"); #endif /* Filter account list. */ TAILQ_INIT(&actaq); TAILQ_FOREACH(a, &conf.accounts, entry) { if (use_account(a, NULL)) TAILQ_INSERT_HEAD(&actaq, a, active_entry); } if (TAILQ_EMPTY(&actaq)) { log_warnx("no accounts found"); res = 1; goto out; } /* Initialise the child process arrays. */ ARRAY_INIT(&children); ARRAY_INIT(&dead_children); #ifdef HAVE_SETPROCTITLE setproctitle("parent"); #endif log_debug2("parent: started, pid is %ld", (long) getpid()); tim = get_time(); res = 0; ARRAY_INIT(&iol); while (!TAILQ_EMPTY(&actaq) || ARRAY_LENGTH(&children) != 0) { log_debug2("parent: %u children, %u dead children", ARRAY_LENGTH(&children), ARRAY_LENGTH(&dead_children)); /* Stop on signal. */ if (sigint || sigterm) break; /* While there is space, start another child. */ while (!TAILQ_EMPTY(&actaq) && (conf.max_accts < 0 || ARRAY_LENGTH(&children) < (u_int) conf.max_accts)) { a = TAILQ_FIRST(&actaq); TAILQ_REMOVE(&actaq, a, active_entry); cfd = xmalloc(sizeof *cfd); cfd->account = a; cfd->op = op; cfd->children = &children; child = child_start(&children, conf.child_uid, conf.child_gid, child_fetch, parent_fetch, cfd, NULL); log_debug2("parent: child %ld (%s) started", (long) child->pid, a->name); } /* Check children and fill the io list. */ ARRAY_CLEAR(&iol); for (i = 0; i < ARRAY_LENGTH(&children); i++) { child = ARRAY_ITEM(&children, i); if (child->io != NULL) ARRAY_ADD(&iol, child->io); } /* Poll the io list. */ if (ARRAY_LENGTH(&iol) != 0) { switch (io_polln(ARRAY_DATA(&iol), ARRAY_LENGTH(&iol), &dead_io, INFTIM, NULL)) { case -1: case 0: break; default: dead_io = NULL; break; } } else { /* No more children. Sleep until all are waited. */ if (wait_children(&children, &dead_children, 0) != 0) res = 1; } /* Check all children for pending privsep messages. */ while ((child = check_children(&children, &i)) != NULL) { /* Handle this message. */ if (privsep_recv(child->io, &msg, &msgbuf) != 0) fatalx("privsep_recv error"); log_debug3("parent: got message type %d, id %u from " "child %ld", msg.type, msg.id, (long) child->pid); if (child->msg(child, &msg, &msgbuf) == 0) continue; /* Child has said it is ready to exit, tell it to. */ log_debug2("parent: sending exit message to child %ld", (long) child->pid); memset(&msg, 0, sizeof msg); msg.type = MSG_EXIT; if (privsep_send(child->io, &msg, NULL) != 0) fatalx("privsep_send error"); } /* Collect any dead children. */ if (sigchld && wait_children(&children, &dead_children, 1) != 0) res = 1; sigchld = 0; /* Close dead buffers (no more data coming now). */ if (dead_io != NULL) { for (i = 0; i < ARRAY_LENGTH(&children); i++) { child = ARRAY_ITEM(&children, i); if (dead_io != child->io) continue; log_debug2("parent: child %ld socket error", (long) child->pid); kill(child->pid, SIGTERM); io_close(child->io); io_free(child->io); child->io = NULL; } } } ARRAY_FREE(&iol); /* Free the dead children. */ for (i = 0; i < ARRAY_LENGTH(&dead_children); i++) { child = ARRAY_ITEM(&dead_children, i); if (child->data != NULL) xfree(child->data); xfree(child); } ARRAY_FREE(&dead_children); if (sigint || sigterm) { act.sa_handler = SIG_IGN; if (sigaction(SIGINT, &act, NULL) < 0) fatal("sigaction failed"); if (sigaction(SIGTERM, &act, NULL) < 0) fatal("sigaction failed"); if (sigint) log_warnx("parent: caught SIGINT. stopping"); else if (sigterm) log_warnx("parent: caught SIGTERM. stopping"); /* Kill the children. */ for (i = 0; i < ARRAY_LENGTH(&children); i++) { child = ARRAY_ITEM(&children, i); kill(child->pid, SIGTERM); io_close(child->io); io_free(child->io); xfree(child); } ARRAY_FREE(&children); /* And wait for them. */ for (;;) { if ((pid = wait(&status)) == -1) { if (errno == ECHILD) break; fatal("wait failed"); } log_debug2("parent: child %ld killed", (long) pid); } res = 1; } tim = get_time() - tim; log_debug2("parent: finished, total time %.3f seconds", tim); out: if (!conf.allow_many && *conf.lock_file != '\0') unlink(conf.lock_file); #ifdef DEBUG COUNTFDS("parent"); /* Free everything. */ if (conf.proxy != NULL) { if (conf.proxy->user != NULL) xfree(conf.proxy->user); if (conf.proxy->pass != NULL) xfree(conf.proxy->pass); if (conf.proxy->server.host != NULL) xfree(conf.proxy->server.host); if (conf.proxy->server.port != NULL) xfree(conf.proxy->server.port); xfree(conf.proxy); } while (!TAILQ_EMPTY(&conf.caches)) { cache = TAILQ_FIRST(&conf.caches); TAILQ_REMOVE(&conf.caches, cache, entry); free_cache(cache); } while (!TAILQ_EMPTY(&conf.accounts)) { a = TAILQ_FIRST(&conf.accounts); TAILQ_REMOVE(&conf.accounts, a, entry); free_account(a); } while (!TAILQ_EMPTY(&conf.rules)) { r = TAILQ_FIRST(&conf.rules); TAILQ_REMOVE(&conf.rules, r, entry); free_rule(r); } while (!TAILQ_EMPTY(&conf.actions)) { t = TAILQ_FIRST(&conf.actions); TAILQ_REMOVE(&conf.actions, t, entry); free_action(t); } xfree(conf.def_user); xfree(conf.cmd_user); xfree(conf.user_home); ARRAY_FREEALL(conf.user_order); xfree(conf.host_name); if (conf.host_fqdn != NULL) xfree(conf.host_fqdn); if (conf.host_address != NULL) xfree(conf.host_address); xfree(conf.conf_file); xfree(conf.lock_file); xfree(conf.tmp_dir); xfree(conf.strip_chars); free_strings(&conf.incl); free_strings(&conf.excl); xmalloc_report(getpid(), "parent"); #endif exit(res); } fdm-1.7+cvs20140912/match-tagged.c0000600000175000017500000000322510577566340014707 0ustar hawkhawk/* $Id: match-tagged.c,v 1.19 2007/03/19 20:04:48 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #include "match.h" int match_tagged_match(struct mail_ctx *, struct expritem *); void match_tagged_desc(struct expritem *, char *, size_t); struct match match_tagged = { "tagged", match_tagged_match, match_tagged_desc }; int match_tagged_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_tagged_data *data = ei->data; struct mail *m = mctx->mail; char *tag; tag = replacestr(&data->tag, m->tags, m, &m->rml); if (match_tag(m->tags, tag) != NULL) { xfree(tag); return (MATCH_TRUE); } xfree(tag); return (MATCH_FALSE); } void match_tagged_desc(struct expritem *ei, char *buf, size_t len) { struct match_tagged_data *data = ei->data; xsnprintf(buf, len, "tagged %s", data->tag.str); } fdm-1.7+cvs20140912/deliver-rewrite.c0000600000175000017500000000646411030761274015467 0ustar hawkhawk/* $Id: deliver-rewrite.c,v 1.54 2008/06/26 18:41:00 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" int deliver_rewrite_deliver(struct deliver_ctx *, struct actitem *); void deliver_rewrite_desc(struct actitem *, char *, size_t); struct deliver deliver_rewrite = { "rewrite", DELIVER_WRBACK, deliver_rewrite_deliver, deliver_rewrite_desc }; int deliver_rewrite_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_rewrite_data *data = ti->data; struct mail *md = &dctx->wr_mail; char *s, *cause, *out, *err; int status; struct cmd *cmd = NULL; char *lbuf; size_t llen; s = replacepath(&data->cmd, m->tags, m, &m->rml, dctx->udata->home); if (s == NULL || *s == '\0') { log_warnx("%s: empty command", a->name); goto error; } log_debug2("%s: rewriting using \"%s\"", a->name, s); md->size = 0; cmd = cmd_start(s, CMD_IN|CMD_OUT|CMD_ONCE, m->data, m->size, &cause); if (cmd == NULL) goto error_cause; log_debug3("%s: %s: started", a->name, s); llen = IO_LINESIZE; lbuf = xmalloc(llen); do { status = cmd_poll( cmd, &out, &err, &lbuf, &llen, conf.timeout, &cause); if (status == -1) { xfree(lbuf); goto error_cause; } if (status != 0) continue; if (err != NULL) log_warnx("%s: %s: %s", a->name, s, err); if (out == NULL) continue; log_debug3("%s: %s: out: %s", a->name, s, out); if (append_line(md, out, strlen(out)) != 0) { log_warnx("%s: %s: failed to resize mail", s, a->name); goto error; } if (md->size > conf.max_size) { log_warnx("%s: %s: oversize mail returned", s, a->name); goto error; } } while (status == 0); status--; xfree(lbuf); if (status != 0) { log_warnx("%s: %s: command returned %d", a->name, s, status); goto error; } if (md->size == 0) { log_warnx("%s: %s: empty mail returned", a->name, s); goto error; } md->body = find_body(md); cmd_free(cmd); xfree(s); return (DELIVER_SUCCESS); error_cause: log_warnx("%s: %s: %s", a->name, s, cause); xfree(cause); error: if (cmd != NULL) cmd_free(cmd); if (s != NULL) xfree(s); return (DELIVER_FAILURE); } void deliver_rewrite_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_rewrite_data *data = ti->data; xsnprintf(buf, len, "rewrite \"%s\"", data->cmd.str); } fdm-1.7+cvs20140912/mail.c0000600000175000017500000002745712152111623013276 0ustar hawkhawk/* $Id: mail.c,v 1.129 2013/05/31 12:12:03 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fdm.h" void mail_free(struct mail *); int mail_open(struct mail *m, size_t size) { m->size = 0; m->space = IO_ROUND(size); m->body = 0; if ((m->base = shm_create(&m->shm, m->space)) == NULL) return (-1); SHM_REGISTER(&m->shm); m->off = 0; m->data = m->base + m->off; strb_create(&m->tags); ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; m->attach = NULL; m->attach_built = 0; return (0); } void mail_send(struct mail *m, struct msg *msg) { struct mail *mm = &msg->data.mail; memcpy(mm, m, sizeof *mm); ARRAY_INIT(&mm->wrapped); mm->wrapchar = '\0'; mm->attach = NULL; } int mail_receive(struct mail *m, struct msg *msg, int destroy) { struct mail *mm = &msg->data.mail; mm->idx = m->idx; mm->tags = m->tags; m->tags = NULL; mm->attach = m->attach; m->attach = NULL; mm->auxfree = m->auxfree; m->auxfree = NULL; mm->auxdata = m->auxdata; m->auxdata = NULL; if (destroy) mail_destroy(m); else mail_close(m); memcpy(m, mm, sizeof *m); if ((m->base = shm_reopen(&m->shm)) == NULL) return (-1); SHM_REGISTER(&m->shm); m->data = m->base + m->off; ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; return (0); } void mail_free(struct mail *m) { if (m->attach != NULL) attach_free(m->attach); if (m->tags != NULL) strb_destroy(&m->tags); ARRAY_FREE(&m->wrapped); m->wrapchar = '\0'; if (m->auxfree != NULL && m->auxdata != NULL) m->auxfree(m->auxdata); } void mail_close(struct mail *m) { mail_free(m); if (m->base != NULL) { SHM_DEREGISTER(&m->shm); shm_close(&m->shm); } } void mail_destroy(struct mail *m) { mail_free(m); if (m->base != NULL) { SHM_DEREGISTER(&m->shm); shm_destroy(&m->shm); } } int mail_resize(struct mail *m, size_t size) { if (SIZE_MAX - m->off < size) fatalx("size too large"); while (m->space <= (m->off + size)) { if ((m->base = shm_resize(&m->shm, 2, m->space)) == NULL) return (-1); m->space *= 2; } m->data = m->base + m->off; return (0); } /* Initialise for iterating over lines. */ void line_init(struct mail *m, char **line, size_t *len) { char *ptr; *line = m->data; ptr = memchr(m->data, '\n', m->size); if (ptr == NULL) *len = m->size; else *len = (ptr - *line) + 1; } /* Move to next line. */ void line_next(struct mail *m, char **line, size_t *len) { char *ptr; *line += *len; if (*line == m->data + m->size) { *line = NULL; return; } ptr = memchr(*line, '\n', (m->data + m->size) - *line); if (ptr == NULL) *len = (m->data + m->size) - *line; else *len = (ptr - *line) + 1; } /* Remove specified header. */ int remove_header(struct mail *m, const char *hdr) { char *ptr; size_t len; if ((ptr = find_header(m, hdr, &len, 0)) == NULL) return (-1); /* Remove the header. */ memmove(ptr, ptr + len, m->size - len - (ptr - m->data)); m->size -= len; m->body -= len; return (0); } /* Insert header, before specified header if not NULL, otherwise at end. */ int printflike3 insert_header(struct mail *m, const char *before, const char *fmt, ...) { va_list ap; char *hdr, *ptr; size_t hdrlen, len, off; u_int newlines; newlines = 1; if (before != NULL) { /* Insert before header. */ ptr = find_header(m, before, &len, 0); if (ptr == NULL) return (-1); off = ptr - m->data; } else { /* Insert at the end. */ if (m->body == 0) { /* * Creating the headers section. Insert at the start, * and add an extra newline. */ off = 0; newlines++; } else { /* * Body points just after the blank line. Insert before * the blank line. */ off = m->body - 1; } } /* Create the header. */ va_start(ap, fmt); hdrlen = xvasprintf(&hdr, fmt, ap); va_end(ap); /* Include the newlines. */ hdrlen += newlines; /* Make space for the header. */ if (mail_resize(m, m->size + hdrlen) != 0) { xfree(hdr); return (-1); } ptr = m->data + off; memmove(ptr + hdrlen, ptr, m->size - off); /* Copy the header. */ memcpy(ptr, hdr, hdrlen - newlines); memset(ptr + hdrlen - newlines, '\n', newlines); m->size += hdrlen; m->body += hdrlen; xfree(hdr); return (0); } /* * Find a header. If value is set, only the header value is returned, with EOL * stripped */ char * find_header(struct mail *m, const char *hdr, size_t *len, int value) { char *ptr; size_t hdrlen; hdrlen = strlen(hdr) + 1; /* include : */ if (m->body < hdrlen || m->size < hdrlen) return (NULL); line_init(m, &ptr, len); while (ptr != NULL) { if (ptr >= m->data + m->body) return (NULL); if (*len >= hdrlen && ptr[hdrlen - 1] == ':') { if (strncasecmp(ptr, hdr, hdrlen - 1) == 0) break; } line_next(m, &ptr, len); } if (ptr == NULL) return (NULL); /* If the entire header is wanted, return it. */ if (!value) return (ptr); /* Otherwise skip the header and following spaces. */ ptr += hdrlen; *len -= hdrlen; while (*len > 0 && isspace((u_char) *ptr)) { ptr++; (*len)--; } /* And trim newlines. */ while (*len > 0 && ptr[*len - 1] == '\n') (*len)--; if (len == 0) return (NULL); return (ptr); } /* Match a header. Same as find_header but uses fnmatch. */ char * match_header(struct mail *m, const char *patt, size_t *len, int value) { char *ptr, *last, *hdr; size_t hdrlen; line_init(m, &ptr, len); while (ptr != NULL) { if (ptr >= m->data + m->body) return (NULL); if ((last = memchr(ptr, ':', *len)) != NULL) { hdrlen = last - ptr; hdr = xmalloc(hdrlen + 1); strlcpy(hdr, ptr, hdrlen + 1); if (fnmatch(patt, hdr, FNM_CASEFOLD) == 0) break; xfree(hdr); } line_next(m, &ptr, len); } if (ptr == NULL) return (NULL); xfree(hdr); /* If the entire header is wanted, return it. */ if (!value) return (ptr); /* Include the : in the length. */ hdrlen++; /* Otherwise skip the header and following spaces. */ ptr += hdrlen; *len -= hdrlen; while (*len > 0 && isspace((u_char) *ptr)) { ptr++; (*len)--; } /* And trim newlines. */ while (*len > 0 && ptr[*len - 1] == '\n') (*len)--; if (len == 0) return (NULL); return (ptr); } /* * Find offset of body. The body is the offset of the first octet after the * separator (\n\n), or zero. */ size_t find_body(struct mail *m) { size_t len; char *ptr; line_init(m, &ptr, &len); while (ptr != NULL) { if (len == 1 && *ptr == '\n') { line_next(m, &ptr, &len); /* If no next line, body is end of mail. */ if (ptr == NULL) return (m->size); /* Otherwise, body is start of line after separator. */ return (ptr - m->data); } line_next(m, &ptr, &len); } return (0); } /* Count mail lines. */ void count_lines(struct mail *m, u_int *total, u_int *body) { size_t len; char *ptr; int flag; flag = 0; *total = *body = 0; line_init(m, &ptr, &len); while (ptr != NULL) { if (flag) (*body)++; if (len == 1 && *ptr == '\n') flag = 1; (*total)++; line_next(m, &ptr, &len); } } /* Append line to mail. Used during fetching. */ int append_line(struct mail *m, const char *line, size_t size) { if (mail_resize(m, m->size + size + 1) != 0) return (-1); if (size > 0) memcpy(m->data + m->size, line, size); m->data[m->size + size] = '\n'; m->size += size + 1; return (0); } char * find_address(char *buf, size_t len, size_t *alen) { char *ptr, *hdr, *first, *last; /* * RFC2822 email addresses are stupidly complicated, so we just do a * naive match which is good enough for 99% of addresses used now. This * code is pretty inefficient. */ /* Duplicate the header as a string to work on it. */ if (len == 0) return (NULL); hdr = xmalloc(len + 1); strlcpy(hdr, buf, len + 1); /* First, replace any sections in "s with spaces. */ ptr = hdr; while (*ptr != '\0') { if (*ptr == '"') { ptr++; while (*ptr != '"' && *ptr != '\0') *ptr++ = ' '; if (*ptr == '\0') break; } ptr++; } /* * Now, look for sections matching: * [< ][A-Za-z0-9._%+-]+@[A-Za-z0-9.\[\]-]+[> ,;]. */ #define isfirst(c) ((c) == '<' || (c) == ' ') #define islast(c) ((c) == '>' || (c) == ' ' || (c) == ',' || (c) == ';') #define isuser(c) (isalnum(c) || \ (c) == '.' || (c) == '_' || (c) == '%' || (c) == '+' || (c) == '-') #define isdomain(c) (isalnum(c) || \ (c) == '.' || (c) == '-' || (c) == '[' || (c) == ']') ptr = hdr + 1; for (;;) { /* Find an @. */ if ((ptr = strchr(ptr, '@')) == NULL) break; /* Find the end. */ last = ptr + 1; while (*last != '\0' && isdomain((u_char) *last)) last++; if (*last != '\0' && !islast((u_char) *last)) { ptr = last + 1; continue; } /* Find the start. */ first = ptr - 1; while (first != hdr && isuser((u_char) *first)) first--; if (first != hdr && !isfirst((u_char) *first)) { ptr = last + 1; continue; } /* If the last is > the first must be < and vice versa. */ if (*last == '>' && *first != '<') { ptr = last + 1; continue; } if (*first == '<' && *last != '>') { ptr = last + 1; continue; } /* If not right at the start, strip first character. */ if (first != hdr) first++; /* Free header copy. */ xfree(hdr); /* Have last and first, return the address. */ *alen = last - first; return (buf + (first - hdr)); } xfree(hdr); return (NULL); } void trim_from(struct mail *m) { char *ptr; size_t len; if (m->data == NULL || m->body == 0 || m->size < 5) return; if (strncmp(m->data, "From ", 5) != 0) return; line_init(m, &ptr, &len); m->size -= len; m->off += len; m->data = m->base + m->off; m->body -= len; } char * make_from(struct mail *m, char *user) { time_t t; char *s, *from = NULL; size_t fromlen = 0; from = find_header(m, "from", &fromlen, 1); if (from != NULL && fromlen > 0) from = find_address(from, fromlen, &fromlen); if (fromlen > INT_MAX) from = NULL; if (from == NULL) { from = user; fromlen = strlen(from); } t = time(NULL); xasprintf(&s, "From %.*s %.24s", (int) fromlen, from, ctime(&t)); return (s); } /* * Sometimes mail has wrapped header lines, this undoubtedly looks neat but * makes them a pain to match using regexps. We build a list of the newlines * in all the wrapped headers in m->wrapped, and can then quickly unwrap them * for regexp matching and wrap them again for delivery. */ u_int fill_wrapped(struct mail *m) { char *ptr; size_t end, off; u_int n; if (!ARRAY_EMPTY(&m->wrapped)) fatalx("already wrapped"); ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; end = m->body; ptr = m->data; n = 0; for (;;) { ptr = memchr(ptr, '\n', m->size - (ptr - m->data)); if (ptr == NULL) break; ptr++; off = ptr - m->data; if (off >= end) break; /* Check if the line starts with whitespace. */ if (!isblank((u_char) *ptr)) continue; /* Save the position. */ ARRAY_ADD(&m->wrapped, off - 1); n++; } return (n); } void set_wrapped(struct mail *m, char ch) { u_int i; if (m->wrapchar == ch) return; m->wrapchar = ch; for (i = 0; i < ARRAY_LENGTH(&m->wrapped); i++) m->data[ARRAY_ITEM(&m->wrapped, i)] = ch; } fdm-1.7+cvs20140912/match-age.c0000600000175000017500000000471310652453150014177 0ustar hawkhawk/* $Id: match-age.c,v 1.39 2007/07/27 20:32:40 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #include "match.h" int match_age_match(struct mail_ctx *, struct expritem *); void match_age_desc(struct expritem *, char *, size_t); struct match match_age = { "age", match_age_match, match_age_desc }; int match_age_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_age_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; time_t then, now; long long diff; /* Get current and mail time. */ now = time(NULL); if (mailtime(m, &then) != 0) { /* Invalid, so return true if testing validity, else false. */ if (data->time < 0) return (MATCH_TRUE); return (MATCH_FALSE); } /* Not invalid, so return false if validity is being tested for. */ if (data->time < 0) return (MATCH_FALSE); /* Work out the time difference. */ diff = difftime(now, then); log_debug2("%s: time difference is %lld (now %lld, then %lld)", a->name, diff, (long long) now, (long long) then); if (diff < 0) { /* Reset all ages in the future to zero. */ diff = 0; } if (data->cmp == CMP_LT && diff < data->time) return (MATCH_TRUE); else if (data->cmp == CMP_GT && diff > data->time) return (MATCH_TRUE); return (MATCH_FALSE); } void match_age_desc(struct expritem *ei, char *buf, size_t len) { struct match_age_data *data = ei->data; const char *cmp = ""; if (data->time < 0) { strlcpy(buf, "age invalid", len); return; } if (data->cmp == CMP_LT) cmp = "<"; else if (data->cmp == CMP_GT) cmp = ">"; xsnprintf(buf, len, "age %s %lld seconds", cmp, data->time); } fdm-1.7+cvs20140912/parent-fetch.c0000600000175000017500000001136111204061551014720 0ustar hawkhawk/* $Id: parent-fetch.c,v 1.17 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" #include "match.h" void parent_fetch_error(struct child *, struct msg *); void parent_fetch_action(struct child *, struct children *, struct deliver_ctx *, struct msg *); void parent_fetch_cmd(struct child *, struct children *, struct mail_ctx *, struct msg *); int parent_fetch(struct child *child, struct msg *msg, struct msgbuf *msgbuf) { struct child_fetch_data *data = child->data; struct children *children = data->children; struct deliver_ctx *dctx; struct mail_ctx *mctx; struct mail *m; switch (msg->type) { case MSG_ACTION: if (msgbuf->buf == NULL || msgbuf->len == 0) fatalx("bad tags"); m = xcalloc(1, sizeof *m); if (mail_receive(m, msg, 0) != 0) { log_warn("parent: can't receive mail"); parent_fetch_error(child, msg); break; } m->tags = msgbuf->buf; dctx = xcalloc(1, sizeof *dctx); dctx->account = msg->data.account; dctx->mail = m; parent_fetch_action(child, children, dctx, msg); break; case MSG_COMMAND: if (msgbuf->buf == NULL || msgbuf->len == 0) fatalx("bad tags"); m = xcalloc(1, sizeof *m); if (mail_receive(m, msg, 0) != 0) { log_warn("parent: can't receive mail"); parent_fetch_error(child, msg); break; } m->tags = msgbuf->buf; mctx = xcalloc(1, sizeof *mctx); mctx->account = msg->data.account; mctx->mail = m; parent_fetch_cmd(child, children, mctx, msg); break; case MSG_DONE: fatalx("unexpected message"); case MSG_EXIT: return (-1); } return (0); } void parent_fetch_error(struct child *child, struct msg *msg) { msg->type = MSG_DONE; msg->data.error = DELIVER_FAILURE; if (privsep_send(child->io, msg, NULL) != 0) fatalx("privsep_send error"); } void parent_fetch_action(struct child *child, struct children *children, struct deliver_ctx *dctx, struct msg *msg) { struct actitem *ti = msg->data.actitem; struct mail *m = dctx->mail; struct mail *md = &dctx->wr_mail; struct child_deliver_data *data; uid_t uid = msg->data.uid; gid_t gid = msg->data.gid; memset(md, 0, sizeof *md); /* * If writing back, open a new mail now and set its ownership so it * can be accessed by the child. */ if (ti->deliver->type == DELIVER_WRBACK) { if (mail_open(md, IO_BLOCKSIZE) != 0) { log_warn("parent: failed to create mail"); parent_fetch_error(child, msg); return; } if (geteuid() == 0 && shm_owner(&md->shm, conf.child_uid, conf.child_gid) != 0) { mail_destroy(md); log_warn("parent: failed to set mail ownership"); parent_fetch_error(child, msg); return; } md->decision = m->decision; } data = xmalloc(sizeof *data); data->child = child; data->msgid = msg->id; data->account = dctx->account; data->hook = child_deliver_action_hook; data->actitem = ti; data->dctx = dctx; data->mail = m; data->name = "deliver"; data->uid = uid; data->gid = gid; child = child_start( children, uid, gid, child_deliver, parent_deliver, data, child); log_debug3("parent: deliver " "child %ld started (uid %lu)", (long) child->pid, (u_long) uid); } void parent_fetch_cmd(struct child *child, struct children *children, struct mail_ctx *mctx, struct msg *msg) { struct mail *m = mctx->mail; struct child_deliver_data *data; uid_t uid = msg->data.uid; gid_t gid = msg->data.gid; data = xmalloc(sizeof *data); data->child = child; data->msgid = msg->id; data->account = mctx->account; data->hook = child_deliver_cmd_hook; data->mctx = mctx; data->cmddata = msg->data.cmddata; data->mail = m; data->name = "command"; data->uid = uid; data->gid = gid; child = child_start( children, uid, gid, child_deliver, parent_deliver, data, child); log_debug3("parent: command " "child %ld started (uid %lu)", (long) child->pid, (u_long) uid); } fdm-1.7+cvs20140912/match-regexp.c0000600000175000017500000000442210650664705014742 0ustar hawkhawk/* $Id: match-regexp.c,v 1.22 2007/07/22 14:29:25 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_regexp_match(struct mail_ctx *, struct expritem *); void match_regexp_desc(struct expritem *, char *, size_t); struct match match_regexp = { "regexp", match_regexp_match, match_regexp_desc }; int match_regexp_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_regexp_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; int res; char *cause; size_t so, eo; so = 0; eo = m->size; switch (data->area) { case AREA_HEADERS: if (m->body == 0) return (MATCH_FALSE); eo = m->body; break; case AREA_BODY: so = m->body; break; case AREA_ANY: break; } log_debug3("%s: matching from %zu to %zu (size=%zu, body=%zu)", a->name, so, eo, m->size, m->body); res = re_block(&data->re, m->data + so, eo - so, &m->rml, &cause); if (res == -1) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (MATCH_ERROR); } if (res == 0) return (MATCH_FALSE); return (MATCH_TRUE); } void match_regexp_desc(struct expritem *ei, char *buf, size_t len) { struct match_regexp_data *data = ei->data; const char *area = NULL; switch (data->area) { case AREA_BODY: area = "body"; break; case AREA_HEADERS: area = "headers"; break; case AREA_ANY: area = "any"; break; } xsnprintf(buf, len, "regexp \"%s\" in %s", data->re.str, area); } fdm-1.7+cvs20140912/match-command.c0000600000175000017500000000622111204061551015047 0ustar hawkhawk/* $Id: match-command.c,v 1.45 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_command_match(struct mail_ctx *, struct expritem *); void match_command_desc(struct expritem *, char *, size_t); struct match match_command = { "command", match_command_match, match_command_desc }; int match_command_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_command_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; struct io *io = mctx->io; struct msg msg; struct msgbuf msgbuf; struct userdata *ud; char *user; set_wrapped(m, '\n'); /* * We are called as the child so to change uid this needs to be done * largely in the parent. */ memset(&msg, 0, sizeof msg); msg.type = MSG_COMMAND; msg.id = m->idx; msg.data.account = a; msg.data.cmddata = data; user = conf.cmd_user; if (data->user.str != NULL) user = replacestr(&data->user, m->tags, m, &m->rml); if ((ud = user_lookup(user, conf.user_order)) == NULL) { log_warnx("%s: bad user: %s", a->name, user); return (MATCH_ERROR); } if (data->user.str != NULL) xfree(user); msg.data.uid = ud->uid; msg.data.gid = ud->gid; update_tags(&m->tags, ud); user_free(ud); msgbuf.buf = m->tags; msgbuf.len = STRB_SIZE(m->tags); mail_send(m, &msg); if (privsep_send(io, &msg, &msgbuf) != 0) fatalx("privsep_send error"); reset_tags(&m->tags); mctx->msgid = msg.id; return (MATCH_PARENT); } void match_command_desc(struct expritem *ei, char *buf, size_t len) { struct match_command_data *data = ei->data; char ret[11]; const char *type; *ret = '\0'; if (data->ret != -1) xsnprintf(ret, sizeof ret, "%d", data->ret); type = data->pipe ? "pipe" : "exec"; if (data->re.str == NULL) { if (data->user.str != NULL) { xsnprintf(buf, len, "%s \"%s\" user \"%s\" returns (%s, )", type, data->cmd.str, data->user.str, ret); } else { xsnprintf(buf, len, "%s \"%s\" returns (%s, )", type, data->cmd.str, ret); } } else { if (data->user.str != NULL) { xsnprintf(buf, len, "%s \"%s\" user \"%s\" returns (%s, \"%s\")", type, data->cmd.str, data->user.str, ret, data->re.str); } else { xsnprintf(buf, len, "%s \"%s\" returns (%s, \"%s\")", type, data->cmd.str, ret, data->re.str); } } } fdm-1.7+cvs20140912/parse-fn.c0000600000175000017500000003416611204061551014063 0ustar hawkhawk/* $Id: parse-fn.c,v 1.27 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" #include "match.h" void free_replstrs(struct replstrs *rsp) { free_strings((struct strings *) rsp); /* XXX */ } char * fmt_replstrs(const char *prefix, struct replstrs *rsp) { return (fmt_strings(prefix, (struct strings *) rsp)); /* XXX */ } void free_strings(struct strings *sp) { u_int i; for (i = 0; i < ARRAY_LENGTH(sp); i++) { xfree(ARRAY_ITEM(sp, i)); } ARRAY_FREE(sp); } char * fmt_strings(const char *prefix, struct strings *sp) { char *buf, *s; size_t len; ssize_t off; u_int i; len = BUFSIZ; buf = xmalloc(len); ENSURE_SIZE(buf, len, strlen(prefix) + 1); off = xsnprintf(buf, len, "%s", prefix); for (i = 0; i < ARRAY_LENGTH(sp); i++) { s = ARRAY_ITEM(sp, i); ENSURE_SIZE(buf, len, off + strlen(s) + 4); off += xsnprintf(buf + off, len - off, "\"%s\" ", s); } if (off == 0) { ENSURE_SIZE(buf, len, off + 1); buf[off] = '\0'; } else buf[off - 1] = '\0'; return (buf); } struct account * find_account(char *name) { struct account *a; TAILQ_FOREACH(a, &conf.accounts, entry) { if (strcmp(a->name, name) == 0) return (a); } return (NULL); } int have_accounts(char *name) { struct account *a; TAILQ_FOREACH(a, &conf.accounts, entry) { if (account_match(name, a->name)) return (1); } return (0); } struct action * find_action(char *name) { struct action *t; TAILQ_FOREACH(t, &conf.actions, entry) { if (strcmp(t->name, name) == 0) return (t); } return (NULL); } struct actions * match_actions(const char *name) { struct action *t; struct actions *ta; ta = xmalloc(sizeof *ta); ARRAY_INIT(ta); TAILQ_FOREACH(t, &conf.actions, entry) { if (action_match(name, t->name)) ARRAY_ADD(ta, t); } return (ta); } struct macro * extract_macro(char *s) { struct macro *macro; char *ptr; const char *errstr; ptr = strchr(s, '='); if (ptr != NULL) *ptr++ = '\0'; if (strlen(s) > MAXNAMESIZE) yyerror("macro name too long: %s", s); macro = xmalloc(sizeof *macro); strlcpy(macro->name, s, sizeof macro->name); switch (*s) { case '$': macro->type = MACRO_STRING; if (ptr == NULL) macro->value.str = xstrdup(""); else macro->value.str = xstrdup(ptr); break; case '%': macro->type = MACRO_NUMBER; if (ptr == NULL) macro->value.num = 0; else { macro->value.num = strtonum(ptr, 0, LLONG_MAX, &errstr); if (errstr != NULL) yyerror("number is %s: %s", errstr, ptr); } break; default: yyerror("invalid macro: %s", s); } return (macro); } struct macro * find_macro(const char *name) { struct macro *macro; TAILQ_FOREACH(macro, &parse_macros, entry) { if (strcmp(macro->name, name) == 0) return (macro); } return (NULL); } void print_rule(struct rule *r) { struct expritem *ei; char s[BUFSIZ], *su, *ss, desc[DESCBUFSIZE]; *s = '\0'; TAILQ_FOREACH(ei, r->expr, entry) { switch (ei->op) { case OP_AND: strlcat(s, "and ", sizeof s); break; case OP_OR: strlcat(s, "or ", sizeof s); break; case OP_NONE: break; } if (ei->inverted) strlcat(s, "not ", sizeof s); ei->match->desc(ei, desc, sizeof desc); strlcat(s, desc, sizeof s); strlcat(s, " ", sizeof s); } if (r->users != NULL) su = fmt_replstrs(" users=", r->users); else su = xstrdup(""); if (r->lambda != NULL) { make_actlist(r->lambda->list, desc, sizeof desc); log_debug2("added rule %u:%s matches=%slambda=%s", r->idx, su, s, desc); } else if (r->actions != NULL) { ss = fmt_replstrs("", r->actions); log_debug2("added rule %u:%s matches=%sactions=%s", r->idx, su, s, ss); xfree(ss); } else log_debug2("added rule %u: matches=%snested", r->idx, s); xfree(su); } void print_action(struct action *t) { char s[BUFSIZ], *su; size_t off; if (t->users != NULL) su = fmt_replstrs(" users=", t->users); else su = xstrdup(""); off = xsnprintf(s, sizeof s, "added action \"%s\":%s deliver=", t->name, su); xfree(su); make_actlist(t->list, s + off, (sizeof s) - off); log_debug2("%s", s); } void make_actlist(struct actlist *tl, char *buf, size_t len) { struct actitem *ti; struct deliver_action_data *data; char desc[DESCBUFSIZE], *s; size_t off; off = 0; TAILQ_FOREACH(ti, tl, entry) { if (ti->deliver != NULL) ti->deliver->desc(ti, desc, sizeof desc); else { data = ti->data; s = fmt_replstrs("", data->actions); xsnprintf(desc, sizeof desc, "action %s", s); xfree(s); } off += xsnprintf(buf + off, len - off, "%u:%s ", ti->idx, desc); if (off >= len) break; } } void free_action(struct action *t) { struct actitem *ti; if (t->users != NULL) { free_replstrs(t->users); ARRAY_FREEALL(t->users); } while (!TAILQ_EMPTY(t->list)) { ti = TAILQ_FIRST(t->list); TAILQ_REMOVE(t->list, ti, entry); free_actitem(ti); } xfree(t->list); xfree(t); } void free_actitem(struct actitem *ti) { if (ti->deliver == &deliver_pipe) { struct deliver_pipe_data *data = ti->data; xfree(data->cmd.str); } else if (ti->deliver == &deliver_rewrite) { struct deliver_rewrite_data *data = ti->data; xfree(data->cmd.str); } else if (ti->deliver == &deliver_write) { struct deliver_write_data *data = ti->data; xfree(data->path.str); } else if (ti->deliver == &deliver_maildir) { struct deliver_maildir_data *data = ti->data; xfree(data->path.str); } else if (ti->deliver == &deliver_remove_header) { struct deliver_remove_header_data *data = ti->data; free_replstrs(data->hdrs); ARRAY_FREEALL(data->hdrs); } else if (ti->deliver == &deliver_add_header) { struct deliver_add_header_data *data = ti->data; xfree(data->hdr.str); xfree(data->value.str); } else if (ti->deliver == &deliver_mbox) { struct deliver_mbox_data *data = ti->data; xfree(data->path.str); } else if (ti->deliver == &deliver_tag) { struct deliver_tag_data *data = ti->data; xfree(data->key.str); if (data->value.str != NULL) xfree(data->value.str); } else if (ti->deliver == &deliver_add_to_cache) { struct deliver_add_to_cache_data *data = ti->data; xfree(data->key.str); xfree(data->path); } else if (ti->deliver == &deliver_remove_from_cache) { struct deliver_remove_from_cache_data *data = ti->data; xfree(data->key.str); xfree(data->path); } else if (ti->deliver == &deliver_smtp) { struct deliver_smtp_data *data = ti->data; if (data->to.str != NULL) xfree(data->to.str); if (data->from.str != NULL) xfree(data->from.str); xfree(data->server.host); xfree(data->server.port); if (data->server.ai != NULL) freeaddrinfo(data->server.ai); } else if (ti->deliver == &deliver_imap) { struct deliver_imap_data *data = ti->data; if (data->user != NULL) xfree(data->user); if (data->pass != NULL) xfree(data->pass); xfree(data->folder.str); xfree(data->server.host); xfree(data->server.port); if (data->server.ai != NULL) freeaddrinfo(data->server.ai); } else if (ti->deliver == NULL) { struct deliver_action_data *data = ti->data; free_replstrs(data->actions); ARRAY_FREEALL(data->actions); } if (ti->data != NULL) xfree(ti->data); xfree(ti); } void free_rule(struct rule *r) { struct rule *rr; struct expritem *ei; if (r->users != NULL) { free_replstrs(r->users); ARRAY_FREEALL(r->users); } if (r->actions != NULL) { free_replstrs(r->actions); ARRAY_FREEALL(r->actions); } if (r->lambda != NULL) free_action(r->lambda); while (!TAILQ_EMPTY(&r->rules)) { rr = TAILQ_FIRST(&r->rules); TAILQ_REMOVE(&r->rules, rr, entry); free_rule(rr); } if (r->expr == NULL) { xfree(r); return; } while (!TAILQ_EMPTY(r->expr)) { ei = TAILQ_FIRST(r->expr); TAILQ_REMOVE(r->expr, ei, entry); if (ei->match == &match_regexp) { struct match_regexp_data *data = ei->data; re_free(&data->re); } else if (ei->match == &match_account) { struct match_account_data *data = ei->data; free_replstrs(data->accounts); ARRAY_FREEALL(data->accounts); } else if (ei->match == &match_command) { struct match_command_data *data = ei->data; xfree(data->cmd.str); if (data->re.str != NULL) re_free(&data->re); } else if (ei->match == &match_tagged) { struct match_tagged_data *data = ei->data; xfree(data->tag.str); } else if (ei->match == &match_string) { struct match_string_data *data = ei->data; xfree(data->str.str); re_free(&data->re); } else if (ei->match == &match_in_cache) { struct match_in_cache_data *data = ei->data; xfree(data->key.str); xfree(data->path); } else if (ei->match == &match_attachment) { struct match_attachment_data *data = ei->data; if (data->op == ATTACHOP_ANYTYPE || data->op == ATTACHOP_ANYNAME) xfree(data->value.str.str); } if (ei->data != NULL) xfree(ei->data); xfree(ei); } xfree(r->expr); xfree(r); } void free_cache(struct cache *cache) { xfree(cache->path); xfree(cache); } void free_account(struct account *a) { if (a->users != NULL) { free_replstrs(a->users); ARRAY_FREEALL(a->users); } if (a->fetch == &fetch_pop3) { struct fetch_pop3_data *data = a->data; if (data->path != NULL) xfree(data->path); xfree(data->user); xfree(data->pass); xfree(data->server.host); xfree(data->server.port); if (data->server.ai != NULL) freeaddrinfo(data->server.ai); } else if (a->fetch == &fetch_pop3pipe) { struct fetch_pop3_data *data = a->data; if (data->path != NULL) xfree(data->path); xfree(data->user); xfree(data->pass); xfree(data->pipecmd); } else if (a->fetch == &fetch_imap) { struct fetch_imap_data *data = a->data; xfree(data->user); xfree(data->pass); free_strings(data->folders); ARRAY_FREEALL(data->folders); xfree(data->server.host); xfree(data->server.port); if (data->server.ai != NULL) freeaddrinfo(data->server.ai); } else if (a->fetch == &fetch_imappipe) { struct fetch_imap_data *data = a->data; if (data->user != NULL) xfree(data->user); if (data->pass != NULL) xfree(data->pass); free_strings(data->folders); ARRAY_FREEALL(data->folders); xfree(data->pipecmd); } else if (a->fetch == &fetch_maildir) { struct fetch_maildir_data *data = a->data; free_strings(data->maildirs); ARRAY_FREEALL(data->maildirs); } else if (a->fetch == &fetch_mbox) { struct fetch_mbox_data *data = a->data; free_strings(data->mboxes); ARRAY_FREEALL(data->mboxes); } else if (a->fetch == &fetch_nntp) { struct fetch_nntp_data *data = a->data; free_strings(data->names); ARRAY_FREEALL(data->names); xfree(data->path); xfree(data->server.host); xfree(data->server.port); if (data->server.ai != NULL) freeaddrinfo(data->server.ai); } if (a->data != NULL) xfree(a->data); xfree(a); } char * expand_path(const char *path, const char *home) { const char *src; char *ptr; struct passwd *pw; src = path; while (isspace((u_char) *src)) src++; if (src[0] != '~') return (NULL); /* ~ */ if (src[1] == '\0') return (xstrdup(home)); /* ~/ */ if (src[1] == '/') { xasprintf(&ptr, "%s/%s", home, src + 2); return (ptr); } /* ~user or ~user/ */ ptr = strchr(src + 1, '/'); if (ptr != NULL) *ptr = '\0'; pw = getpwnam(src + 1); if (pw == NULL || pw->pw_dir == NULL || *pw->pw_dir == '\0') { endpwent(); return (NULL); } if (ptr == NULL) ptr = xstrdup(pw->pw_dir); else xasprintf(&ptr, "%s/%s", pw->pw_dir, ptr + 1); endpwent(); return (ptr); } void find_netrc(const char *host, char **user, char **pass) { char *cause; if (find_netrc1(host, user, pass, &cause) != 0) yyerror("%s", cause); } int find_netrc1(const char *host, char **user, char **pass, char **cause) { FILE *f; if ((f = netrc_open(conf.user_home, cause)) == NULL) return (-1); if (netrc_lookup(f, host, user, pass) != 0) { xasprintf(cause, "error reading .netrc"); return (-1); } if (user != NULL) { if (*user == NULL) { xasprintf(cause, "can't find user for \"%s\" in .netrc", host); goto bad; } if (**user == '\0') { xasprintf(cause, "invalid user"); goto bad; } } if (pass != NULL) { if (*pass == NULL) { xasprintf(cause, "can't find pass for \"%s\" in .netrc", host); goto bad; } if (**pass == '\0') { xasprintf(cause, "invalid pass"); goto bad; } } fclose(f); return (0); bad: fclose(f); return (-1); } char * run_command(const char *s, const char *file) { struct cmd *cmd; char *lbuf, *sbuf; size_t llen, slen; char *cause, *out, *err; int status; if (*s == '\0') yyerror("empty command"); log_debug3("running command: %s", s); if ((cmd = cmd_start(s, CMD_OUT, NULL, 0, &cause)) == NULL) yyerror("%s: %s", s, cause); llen = IO_LINESIZE; lbuf = xmalloc(llen); slen = 1; sbuf = xmalloc(slen); *sbuf = '\0'; do { status = cmd_poll( cmd, &out, &err, &lbuf, &llen, DEFTIMEOUT, &cause); if (status == -1) { cmd_free(cmd); yyerror("%s: %s", s, cause); } if (status == 0) { if (err != NULL) { log_warnx("%s: %s: %s", file, s, err); } if (out != NULL) { slen += strlen(out) + 1; sbuf = xrealloc(sbuf, 1, slen); strlcat(sbuf, out, slen); strlcat(sbuf, "\n", slen); } } } while (status == 0); status--; xfree(lbuf); if (status != 0) { cmd_free(cmd); yyerror("%s: command returned %d", s, status); } cmd_free(cmd); slen--; while (slen > 0 && sbuf[slen - 1] == '\n') { sbuf[slen - 1] = '\0'; slen--; } return (sbuf); } fdm-1.7+cvs20140912/cache-op.c0000600000175000017500000000655411204061550014026 0ustar hawkhawk/* $Id: cache-op.c,v 1.3 2009/05/17 19:20:08 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" __dead void cache_op_add(int, char **); __dead void cache_op_remove(int , char **); __dead void cache_op_list(int, char **); __dead void cache_op_dump(int, char **); __dead void cache_op_clear(int, char **); __dead void cache_op(int argc, char **argv) { char *cmd; if (argc < 1) usage(); cmd = argv[0]; argc--; argv++; if (strncmp(cmd, "add", strlen(cmd)) == 0) cache_op_add(argc, argv); if (strncmp(cmd, "remove", strlen(cmd)) == 0) cache_op_remove(argc, argv); if (strncmp(cmd, "list", strlen(cmd)) == 0) cache_op_list(argc, argv); if (strncmp(cmd, "dump", strlen(cmd)) == 0) cache_op_dump(argc, argv); if (strncmp(cmd, "clear", strlen(cmd)) == 0) cache_op_clear(argc, argv); usage(); } __dead void cache_op_add(int argc, char **argv) { TDB_CONTEXT *db; if (argc != 2) usage(); if ((db = db_open(argv[0])) == NULL) { log_warn("%s", argv[0]); exit(1); } if (db_add(db, argv[1]) != 0) { log_warnx("%s: cache error", argv[0]); exit(1); } exit(0); } __dead void cache_op_remove(int argc, char **argv) { TDB_CONTEXT *db; if (argc != 2) usage(); if ((db = db_open(argv[0])) == NULL) { log_warn("%s", argv[0]); exit(1); } if (!db_contains(db, argv[1])) { log_warnx("%s: key not found: %s", argv[0], argv[1]); exit(1); } if (db_remove(db, argv[1]) != 0) { log_warnx("%s: cache error", argv[0]); exit(1); } exit(0); } __dead void cache_op_list(int argc, char **argv) { struct cache *cache; TDB_CONTEXT *db; if (argc == 1) { if ((db = db_open(argv[0])) == NULL) { log_warn("%s", argv[0]); exit(1); } log_info("%s: %u keys", argv[0], db_size(db)); db_close(db); exit(0); } if (argc != 0) usage(); TAILQ_FOREACH(cache, &conf.caches, entry) { if ((cache->db = db_open(cache->path)) == NULL) { log_warn("%s", cache->path); exit(1); } log_info("%s: %u keys", cache->path, db_size(cache->db)); db_close(cache->db); } exit(0); } __dead void cache_op_dump(int argc, char **argv) { TDB_CONTEXT *db; if (argc != 1) usage(); if ((db = db_open(argv[0])) == NULL) { log_warn("%s", argv[0]); exit(1); } if (db_print(db, log_info) != 0) { log_warnx("%s: cache error", argv[0]); exit(1); } exit(0); } __dead void cache_op_clear(int argc, char **argv) { TDB_CONTEXT *db; if (argc != 1) usage(); if ((db = db_open(argv[0])) == NULL) { log_warn("%s", argv[0]); exit(1); } if (db_clear(db) != 0) { log_warnx("%s: cache error", argv[0]); exit(1); } exit(0); } fdm-1.7+cvs20140912/fdm.conf.50000600000175000017500000006460312142176241013770 0ustar hawkhawk.\" $Id: fdm.conf.5,v 1.106 2013/05/07 13:07:45 nicm Exp $ .\" .\" Copyright (c) 2006 Nicholas Marriott .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd August 21, 2006 .Dt FDM.CONF 5 .Os .Sh NAME .Nm fdm.conf .Nd "fdm configuration file" .Sh DESCRIPTION This manual page describes the .Xr fdm 1 configuration file. It defines .Em accounts from which to fetch mail, a number of possible .Em actions to take, and .Em rules connecting a regexp with an action. The file is parsed once from top to bottom, so action and account definitions must appear before they are referenced in a rule. Rules are evaluated from first to last and (unless overridden by the .Ic continue keyword) evaluation stops at the first match. .Pp The file has the following format: .Pp Empty lines and lines beginning with the .Sq # character are ignored. .Pp Regexps and strings must be enclosed in double quotes. Special characters in regexps and strings (including passwords) must be escaped. Note that this may mean double-escaping in regexps. .Pp Possible commands are covered in the following sections. .Sh OPTIONS Options are configured using the .Ic set command. It may be followed by the following options, one per command: .Pp .Bl -tag -width Ds .It Ic maximum-size Ar size This is used to set the maximum size of a mail. Mails larger than this limit are dropped and, if applicable, not deleted from the server. .Pp The size may be specified as a plain number in bytes or with a suffix of .Ql K for kilobytes, .Ql M for megabytes or .Ql G for gigabytes. The default is 32 megabytes and the maximum is one gigabyte. .It Ic delete-oversized If this option is specified, .Xr fdm 1 attempts to delete messages which exceed .Ic maximum-size , and continue. If it is not specified, oversize messages are a fatal error and cause .Xr fdm 1 to abort. .Pp Note that .Xr fdm 1 may have a number of messages queued (up to the .Ic queue-high setting, doubled for rewrite, per account), so this setting and the .Ic queue-high option should be set after consideration of the space available in the temporary folder and the implications should .Xr fdm 1 abort due to the space becoming full. .It Ic queue-high Ar number This sets the maximum number of messages .Xr fdm 1 will hold simultaneously. .Xr fdm 1 will attempt to process previously queued messages as the next is being fetched. Once this limit is reached, no further messages wil be fetched until the number of messages held drops to the .Ic queue-low value. .It Ic queue-low Ar number This is the length to which the message queue must drop before fetching continues after the .Ic queue-high limit has been reached. .It Ic allow-multiple If this option is specified, .Xr fdm 1 does not attempt to create a lock file and allows multiple instances to run simultaneously. .It Ic lock-file Ar path This sets an alternative lock file. The default is .Pa ~/.fdm.lock for non-root users and .Pa /var/db/fdm.lock for root. .It Ic command-user Ar user This specifies the user used to run .Ic exec and .Ic pipe actions. By default it is the user who invoked fdm. .It Ic default-user Ar user This sets the default user to change to before delivering mail, if .Xr fdm 1 is running as root and no alternative user is specified as part of the action or rule. This option may be overridden with the .Fl u switch on the command line. A default user must be given if running as root. .It Ic lookup-order Ar location Ar ... This specifies the order in which to do user lookup from left to right. Possible types are .Em passwd to use the .Xr passwd 5 file, or .Em courier to use Courier authlib (if support is compiled). .It Ic lock-types Ar type Ar ... This specifies the locks to be used for mbox locking. Possible types are .Em fcntl , .Em flock , and .Em dotlock . The .Em flock and .Em fcntl types are mutually exclusive. The default is .Em flock . .It Ic proxy Ar url This instructs .Xr fdm 1 to proxy all connections through .Ar url . HTTP and SOCKS5 proxies are supported at present (URLs of the form .Em http://host[:port] or .Em socks://[user:pass@]host[:port]) . No authentication is supported for HTTP. .It Ic unmatched-mail Ar drop | Ar keep This option controls what .Xr fdm 1 does with mail that reaches the end of the ruleset (mail that matches no rules or matches only rules with the .Ic continue keyword). .Ar drop will cause such mail to be discarded, and .Ar keep will attempt to leave the mail on the server. The default is to keep the mail and log a warning that it reached the end of the ruleset. .It Ic purge-after Ar count The .Ic purge-after option makes .Xr fdm 1 attempt to purge deleted mail from the server (if supported) after .Ar count mails have been retrieved. This is useful on unreliable connections to limit the potential number of mails refetched if the connetion drops, but note that it can incur a considerable speed penalty. .It Ic no-received If this option is present, .Xr fdm 1 will not insert a .Sq Received header into each mail. .It Ic no-create If this option is set, .Xr fdm 1 will not attempt to create maildirs and mboxes or missing elements of their paths. .It Ic file-umask Ic user | Ar umask This specifies the .Xr umask 2 to use when creating files. .Ic user means to use the umask set when .Xr fdm 1 is started, or .Ar umask may be specified as a three-digit octal number. The default is 077. .It Ic file-group Ic user | Ar group This option allows the default group ownership of files and directories created by .Xr fdm 1 to be specified. .Ar group may be a group name string or a numeric gid. If .Ic user is used, or this option does not appear in the configuration file, .Xr fdm 1 does not attempt to set the group of new files and directories. .It Ic timeout Ar time This controls the maximum time to wait for a server to send data before closing a connection. The default is 900 seconds. .It Ic verify-certificates Instructs .Xr fdm 1 to verify SSL certificates for all SSL connections. .El .Sh INCLUDING FILES Further configuration files may be including using the .Ic include command: .Bl -tag -width Ds .It Ic include Ar path .El .Pp The file to include is searched for first as an absolute path and then relative to the directory containing the main configuration file. .Sh MACROS Macros may be defined using the following syntax: .Bl -tag -width Ds .It Ar $name Ic = Ar string .It Ar %name Ic = Ar number .El .Pp Macros are prefixed with $ to indicate a string value and % to indicate a numeric value. Once defined, a macro may be used in any place a string or number is expected. Macros may be embedded in strings by surrounding their name (after the $ or %) with {}s, like so: .Bd -ragged -offset indent "abc ${mymacro} %{anothermacro} def" .Ed .Pp The .Ic ifdef , .Ic ifndef and .Ic endif keywords may be used to conditionally parse a section of the configuration file depending on whether or not the macro given exists or does not exist. .Ic ifdef and .Ic ifndef blocks may be nested. .Sh SHELL COMMANDS The result of a shell command may be used at any point a string or number is expected by wrapping it in $() or %(). If the former is used, the command result is used as a string; if the latter, it is converted to an integer. Shell commands are executed when the configuration file is parsed. .Sh ACCOUNTS The .Ic account command is used to instruct .Xr fdm 1 to fetch mail from an account. The syntax is: .Bl -tag -width Ds .It Xo Ic account Ar name .Op Ar users .Op Ic disabled .Ar type Op Ar args .Op Ic keep .Xc .El .Pp The .Ar name argument is a string specifying a name for the account. The optional .Ar users argument has the following form: .Bl -tag -width Ds .It Xo Ic user Ar user | Ic users .Li { .Ar user ... .Li } .Xc .El .Pp The first two options specify a user or list of users as which the mail should be delivered when an action is executed. If no users are specified, the default user (set with .Ic set Ic default-user ) is used. Users specified as part of the account definition may be overridden by similar arguments to action definitions or on match rules. If .Xr fdm 1 is run as non-root, it will still execute any actions once for each user, but will be unable to change to that user so the action will be executed multiple times as the current user. .Pp The .Ic disabled keyword instructs .Xr fdm 1 to ignore this account unless it is explicitly enabled with a .Fl a option on the command line. If the .Ic keep keyword is specified, all mail collected from this account is kept (not deleted) even if it matches a .Ic drop action. .Pp Supported account types and arguments are: .Pp .Bl -tag -width Ds .It Ic stdin This account type reads mail from .Em stdin , if it is connected to a pipe. This may be used to deliver mail from .Xr sendmail 8 , see .Xr fdm 1 for details. .It Xo Ic pop3 Ic server Ar host .Op Ic port Ar port .Op Ic user Ar user .Op Ic pass Ar pass .Op Ar only .Op Ic no-apop .Op Ic no-uidl .Xc .It Xo Ic pop3s Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Op Ar only .Op Ic no-apop .Op Ic no-uidl .Op Ic no-verify .Op Ic no-tls1 .Xc These statements define a POP3 or POP3S account. The .Ar userpass element has the following form: .Bl -tag -width Ds .It Xo .Op Ic user Ar user .Op Ic pass Ar pass .Xc .El .Pp The .Ar host , .Ar user and .Ar pass arguments must be strings. If the user or pass is not provided, .Xr fdm 1 attempts to look it up in the .Pa ~/.netrc file (see .Xr ftp 1 for details of the file format). The port option may be either a string which will be looked up in the .Xr services 5 database, or a number. If it is omitted, the default port (110 for POP3, 995 for POP3S) is used. .Pp The .Ar only option takes the form: .Bl -tag -width Ds .It Xo .Op Ic new-only | Ic old-only .Ic cache Ar path .Xc .El .Pp .Ic new-only fetches only mail not previously fetched, and .Ic old-only is the inverse: it fetches only mail that has been fetched before. The cache file is used to save the state of the POP3 mailbox. The .Ic no-apop flag forces .Xr fdm 1 not to use the POP3 APOP command for authentication, and the .Ic no-verify keyword instructs .Xr fdm 1 to skip SSL certificate validation for this account. The .Ic no-uidl keyword makes .Xr fdm 1 not use the UIDL command to retrieve mails. This is mainly useful for broken POP3 servers. .Pp The .Ic no-tls1 keyword instructs .Xr fdm 1 not to use the TLSv1 protocol with SSL connections. Some broken servers will fail in the handshake phase if the .Ic tls1 flag is not unset. .It Xo Ic pop3 Ic pipe Ar command .Op Ar userpass .Op Ar only .Op Ic no-apop .Xc This account type uses the POP3 protocol piped through .Ar command , such as .Xr ssh 1 . If the command produces any output to .Em stderr , it is logged. For POP3 over a pipe, providing a user and password is not optional and it may not be read from .Pa ~/.netrc . .It Xo Ic imap Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Op Ic folder Ar name .Op Ar only .Op Ic no-cram-md5 .Op Ic no-login .Xc .It Xo Ic imap Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Op Ic folders .Li { .Ar name ... .Li } .Op Ar only .Xc .It Xo Ic imaps Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Op Ar folders .Op Ar only .Op Ic no-verify .Op Ic no-tls1 .Op Ic no-cram-md5 .Op Ic no-login .Xc These define an IMAP or IMAPS account. The parameters are as for a POP3 or POP3S account, aside from the additional .Ar folders option which sets the name of the folder or folders to use (the default is to fetch from the inbox). This has the form: .Bl -tag -width Ds .It Xo Ic folder Ar name | Ic folders .Li { .Ar name Ar ... .Li } .Xc .El .Pp The default ports used are 143 for IMAP and 993 for IMAPS. For IMAP, the .Ar only item consists only of one of the keywords .Ic new-only or .Ic old-only - a cache file is not required. .Pp Options .Ic no-cram-md5 and .Ic no-login disable the given authentication method. The default is to use CRAM-MD5 if it is available, or LOGIN otherwise. .It Xo Ic imap Ic pipe Ar command .Op Ar userpass .Op Ar folders .Op Ar only .Xc As with .Ic pop3 .Ic pipe , this account type uses the IMAP protocol piped through .Ar command . If the optional IMAP .Ar user and .Ar pass are supplied, they will be used if necessary, but if one is provided, both must be - using .Pa ~/.netrc is not permitted. .It Ic maildir Ar path .It Xo Ic maildirs .Li { .Ar path ... .Li } .Xc These account types instruct .Xr fdm 1 to fetch mail from the maildir or maildirs specified. This allows .Xr fdm 1 to be used to filter mail, fetching from a maildir and deleting (dropping) unwanted mail, or delivering mail to another maildir or to an mbox. .Pp Mail fetched from a maildir is tagged with a maildir tag containing the basename of the mail file. .It Ic mbox Ar path .It Xo Ic mboxes .Li { .Ar path ... .Li } .Xc These are similar to .Ic maildir and .Ic maildirs , but cause .Xr fdm 1 to fetch mail from an mbox or set of mboxes. .Pp Mail fetched from a mbox is tagged with a mbox tag containing the basename of the mbox file. .It Xo Ic nntp Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Ic group Ar group .Ic cache Ar cache .Xc .It Xo Ic nntp Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Ic groups .Li { .Ar group ... .Li } .Ic cache Ar cache .Xc .It Xo Ic nntps Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Ic group Ar group .Ic cache Ar cache .Op Ic no-tls1 .Xc .It Xo Ic nntps Ic server Ar host .Op Ic port Ar port .Op Ar userpass .Ic groups .Li { .Ar group ... .Li } .Ic cache Ar cache .Op Ic no-tls1 .Xc An NNTP account. Articles are fetched from the specified group or groups and delivered. The index and message-id of the last article fetched in each group is saved in the specified cache file. When .Xr fdm 1 is run again, fetching begins at the cached article. Note that the .Ic keep option is completely ignored for NNTP accounts - all mail is kept, and the cache is always updated. .El .Sh TAGGING As mail is processed by .Xr fdm 1 , it is tagged with a number of name/value pairs. Some tags are added automatically, and mail may also be tagged explicitly by the user using the .Ic tag action. Tags may be inserted in most strings in a similar manner to macros, except tags are processed at runtime rather than as the configuration file is parsed. A tag's value is inserted by wrapping its name in %[], for example: .Bl -tag -width Ds .It "abc%[account]def" .It "%[hour]:%[minute]:%[second]" .El .Pp The default tags also have a single-letter shorthand. Including a nonexistent tag in a string is equivalent to including a tag with an empty value, so "abc%[nonexistent]def" will be translated to "abcdef". .Pp The automatically added tags are: .Pp .Bl -tag -width Ds -offset indent -compact .It account (%a) The name of the account from which the mail was fetched. .It home (%h) The delivery user's home directory. .It uid (%n) The delivery user's uid. .It action (%t) The name of the last action executed for this mail. .It user (%u) The delivery user's username. .It hour (%H) The current hour (00-23). .It minute (%M) The current minute (00-59). .It second (%S) The current second (00-59). .It day (%d) The current day of the month (01-31). .It month (%m) The current month (01-12). .It year (%y) The current year. .It year2 The current year as two digits. .It dayofweek (%W) The current day of the week (0-6, Sunday is 0). .It dayofyear (%Y) The current day of the year (001-366). .It quarter (%Q) The current quarter (1-4). .It rfc822date The current date in RFC822 format. .It mail_hour The hour from the mail's date header, if it exists and is valid, otherwise the current time. .It mail_minute The minute from the mail's date header. .It mail_second The second from the mail's date header. .It mail_day The day from the mail's date header. .It mail_month The month from the mail's date header. .It mail_year The year from the mail's date header. .It mail_year2 The same as two digits. .It mail_dayofweek The day of the week from the mail's date header. .It mail_dayofyear The day of the year from the mail's date header. .It mail_quarter The quarter (1-4) from the mail's date header. .It mail_rfc822date The mail's date in RFC822 format. .It hostname The local hostname. .El .Pp In addition, the shorthand %% is replaced with a literal %, and %0 to %9 are replaced with the result of any bracket expressions in the last regexp. .Sh CACHES .Xr fdm 1 can maintain a cache file with a set of user-defined strings. In order to use caches, .Xr fdm 1 must have been compiled with them enabled. Caches are declared with the .Ic cache keyword: .Bl -tag -width Ds .It Xo Ic cache Ar path .Op Ic expire Ar age .Xc .El .Pp The .Ar path is the location of the cache file. If the .Ic expire keyword is specified, items in the cache are removed after they reach the age specified. .Ar age may be given unadorned in seconds, or followed by one of the modifiers: .Em seconds , .Em hours , .Em minutes , .Em days , .Em months or .Em years . .Pp Caches must be declared before they are used. Items are added to caches using the .Ic add-to-cache action, removed using the .Ic remove-from-cache action, and searched for using the .Ic in-cache condition; see below for information on these. .Sh ACTIONS The .Ic action command is used to define actions. These may be specified by name in rules (see below) to perform some action on a mail. The syntax is: .Bl -tag -width Ds .It Xo Ic action Ar name Op Ar users .Ar action .Xc .It Xo Ic action Ar name Op Ar users .Li { .Ar action ... .Li } .Xc .El .Pp The .Ar name is a string defining a name for the action. The .Ar users argument has the same form as for an account definition. An action's user setting may be overridden in the matching rule. .Pp The possible values for .Ar action are listed below. If multiple actions are specified they are executed once in the order specified, for each user. .Bl -tag -width Ds .It Ic drop Discard the mail. .It Ic keep Keep the mail, do not remove it from the account. .It Xo Ic tag Ar string .Op Ic value Ar value .Xc This tags mail with .Ar string , and optionally .Ar value , which may be matched using the .Ic tagged or .Ic string conditions. .It Xo Ic maildir Ar path .Xc Save the mail to the maildir specified by .Ar path . If the maildir or any part of its path does not exist, it is created, unless the .Ic no-create option is set. .Pp Mail delivered to a maildir is tagged with a mail_file tag containing the full path of the mail file. .It Xo Ic mbox Ar path Op Ic compress .Xc Append the mail to the mbox at .Ar path . If .Ic compress is specified, .Xr fdm 1 will add .Sq .gz to .Ar path and attempt to write mail using .Xr gzip 1 compression. If the mbox or any part of its path does not exist, it is created, unless the .Ic no-create option is set. .Pp Mail delivered to an mbox is tagged with a mbox_file tag containing the path of the mbox. .It Xo Ic exec Ar command .Xc Execute .Ar command . .It Xo Ic pipe Ar command .Xc Pipe the mail to .Ar command . .Ic exec and .Ic pipe commands are run as the command user. .It Xo Ic write Ar path .Xc Write the mail to .Ar path . .It Xo Ic append Ar path .Xc Append the mail to .Ar path . .It Xo Ic smtp Ic server Ar host .Op Ic port Ar port .Op Ic from Ar from .Op Ic to Ar to .Xc Connect to an SMTP server and attempt to deliver the mail to it. If .Ar from or .Ar to is specified, they are passed to the server in the MAIL FROM or RCPT TO commands. If not, the current user and host names are used. .It Xo Ic rewrite Ar command .Xc Pipe the entire mail through .Ar command to generate a new mail and use that mail for any following actions or rules. An example of the .Ic rewrite action is: .Bd -literal -offset indent action "cat" pipe "cat" action "rewrite" rewrite "sed 's/bob/fred/g'" # this rule will rewrite the message match all action "rewrite" continue # this rule will cat the rewritten message match all action "cat" .Ed .It Ic add-header Ar name Ic value Ar value Add a header .Ar name with contents .Ar value . .It Ic remove-header Ar name .It Xo Ic remove-headers .Li { .Ar name ... .Li } .Xc Remove all occurances of headers matching the .Xr fnmatch 3 pattern .Ar name . .It Ic stdout Write the mail to .Em stdout . .It Ic add-to-cache Ar path Ic key Ar key This action adds the string .Ar key to the cache specified by .Ar path . If .Ar key already exists in the cache, it is replaced. .It Ic remove-from-cache Ar path Ic key Ar key Remove the string .Ar key from the cache .Ar path , if a matching key is present. .It Ic action Ar name This invokes another named action. A maximum of five actions may be called in a sequence. .El .Sh RULES Rules are specified using the .Ic match keyword. It has the following basic form: .Bl -tag -width Ds .It Xo Ic match .Ar condition .Op Ic and | Ic or Ar condition ... .Op Ar users .Ar actions .Op Ic continue .Xc .El .Pp The .Ar condition argument may be one of: .Bl -tag -width Ds .It Ic all Matches all mail. .It Ic matched Matches only mail that has matched a previous rule and been passed on with .Ic continue . .It Ic unmatched The opposite of .Ic matched : matches only mails which have matched no previous rules. .It Xo Ic account Ar name | Ic accounts .Li { .Ar name ... .Li } .Xc Matches only mail fetched from the named account or accounts. The account names may include shell glob wildcards to match multiple accounts, as with the .Fl a and .Fl x command line options. .It Ic tagged Ar string Matches mails tagged with .Ar string . .It Xo Op Ic case .Ar regexp .Op Ic in Ic headers | Ic in body .Xc Specifies a regexp against which each mail should be matched. The regexp matches may be restricted to either the headers or body of the message by specifying either .Ic in headers or .Ic in body . The .Ic case keyword forces the regexp to be matched case-sensitively: the default is case-insensitive matching. .It Xo Ic exec Ar command .Op Ic user Ar user .Ic returns .Li ( .Ar return code , .Ar stdout regexp ) .Xc .It Xo Ic pipe Ar command .Op Ic user Ar user .Ic returns .Li ( .Ar return code , .Op Ic case .Ar stdout regexp ) .Xc These two conditions execute a .Ar command and test its return value and output. The .Ar return code argument is the numeric return code expected and .Ar stdout regexp is a regexp to be tested against the output of the command to .Em stdout . Either of these two arguments may be omitted: if both are specified, both must match for the condition to be true. The .Ic pipe version will pipe the mail to the command's .Em stdin when executing it. If a user is specified, .Xr fdm 1 will change to that user before executing the command, otherwise the current user (or root if started as root) is used. .It Xo Ic size .Li < .Ar number .Xc .It Xo Ic size .Li > .Ar number .Xc Compare the mail size with .Ar number . .It Xo Ic string Ar string Ic to .Op Ic case .Ar regexp .Xc Match .Ar string against .Ar regexp . .It Xo Ic age .Li < .Ar time .Xc .It Xo Ic age .Li > .Ar time .Xc The .Ic age condition examines the mail's date header to determine its age, and matches if the mail is older (>) or newer (<) than the time specified. The time may be given as a simple number in seconds, or followed by the word .Em seconds , .Em hours , .Em minutes , .Em days , .Em months or .Em years to specify a time in different units. .It Ic in-cache Ar path Ic key Ar key This condition evaluates to true if the string .Ar key is in the cache at .Ar path . .It Xo Ic attachment Ic count .Li < .Ar number .Xc .It Xo Ic attachment Ic count .Li > .Ar number .Xc .It Xo Ic attachment Ic count .Li == .Ar number .Xc .It Xo Ic attachment Ic count .Li != .Ar number .Xc These conditions match if the mail possesses a number of attachments less than, greater than, equal to or not equal to .Ar number . .It Xo Ic attachment Ic total-size .Li < .Ar size .Xc .It Xo Ic attachment Ic total-size .Li > .Ar size .Xc Matches if the total size of all attachments is smaller or larger than .Ar size . .It Xo Ic attachment Ic any-size .Li < .Ar size .Xc .It Xo Ic attachment Ic any-size .Li > .Ar size .Xc Compare each individual attachment on a mail to .Ar size and match if any of them are smaller or larger. .It Xo Ic attachment Ic any-type .Ar string .Xc .It Xo Ic attachment Ic any-name .Ar string .Xc Match true if any of a mail's attachments possesses a MIME type or filename that matches .Ar string . .Xr fnmatch 3 wildcards may be used. .El .Pp Multiple conditions may be chained together using the .Ic and or .Ic or keywords. The conditions are tested from left to right. Any condition may be prefixed by the .Ic not keyword to invert it. .Pp The optional .Ar users argument to the first form has the same syntax as for an .Ic action definition. A rule's user list overrides any users given as part of the actions. .Pp The .Ar actions list specifies the actions to perform when the rule matches a mail. It is either of a similar form: .Bl -tag -width Ds .It Xo Ic action Ar name | Ic actions .Li { .Ar name ... .Li } .Xc .El .Pp Or may specify a number of actions inline (lambda actions): .Pp .Bl -tag -width Ds .It Ic action Ar action .It Xo Ic action .Li { .Ar action ... .Li } .Xc .El .Pp In the latter case, .Ar action follows the same form as described in the ACTIONS section. The actions are performed from first to last in the order they are specified in the rule definition. .Pp If the .Ic continue keyword is present, evaluation will not stop if this rule is matched. Instead, .Xr fdm 1 will continue to match further rules after performing any actions for this rule. .Sh NESTED RULES Rules may be nested by specifying further rules in braces: .Bl -tag -width Ds .It Xo Ic match .Ar condition .Op Ic and | Ic or Ar condition ... .Li { .Xc .It Ic match Ar ... .It Li } .El .Pp The inner rules will not be evaluated unless the outer one matches. Rules may be multiply nested. Note that the outer rule does not count as a match for the purposes of the .Ic matched and .Ic unmatched conditions. .Sh FILES .Bl -tag -width "/var/db/fdm.lockXXX" -compact .It Pa ~/.fdm.conf default .Nm configuration file .It Pa /etc/fdm.conf default system-wide configuration file .It Pa ~/.fdm.lock default lock file .It Pa /var/db/fdm.lock lock file for root user .El .Sh SEE ALSO .Xr fdm 1 , .Xr re_format 7 .Sh AUTHORS .An Nicholas Marriott Aq nicm@users.sourceforge.net fdm-1.7+cvs20140912/shm-mmap.c0000600000175000017500000001144711204053061014061 0ustar hawkhawk/* $Id: shm-mmap.c,v 1.21 2009/05/17 18:23:45 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "fdm.h" /* * This implements shared memory using mmap'd files in TMPDIR. */ int shm_expand(struct shm *, size_t); char shm_block[BUFSIZ]; #ifdef MAP_NOSYNC #define SHM_FLAGS MAP_SHARED|MAP_NOSYNC #else #define SHM_FLAGS MAP_SHARED #endif #define SHM_PROT PROT_READ|PROT_WRITE /* Work out shm path. */ char * shm_path(struct shm *shm) { static char path[MAXPATHLEN]; if (ppath(path, sizeof path, "%s/%s", conf.tmp_dir, shm->name) != 0) return (NULL); return (path); } /* Expand or reduce shm file to size. */ int shm_expand(struct shm *shm, size_t size) { ssize_t n; if (size == shm->size) return (0); if (size < shm->size) return (ftruncate(shm->fd, size) != 0); if (lseek(shm->fd, shm->size, SEEK_SET) == -1) return (-1); /* * Fill the file using write(2) to avoid fragmentation problems on * FreeBSD and also to detect disk full. */ while (size > sizeof shm_block) { if ((n = write(shm->fd, shm_block, sizeof shm_block)) == -1) return (-1); if (n != sizeof shm_block) { errno = EIO; return (-1); } size -= sizeof shm_block; } if (size > 0) { if ((n = write(shm->fd, shm_block, size)) == -1) return (-1); if ((size_t) n != size) { errno = EIO; return (-1); } } /* * Sync the fd, should hopefully fail if disk full. */ if (fsync(shm->fd) != 0) return (-1); return (0); } /* Create an shm file and map it. */ void * shm_create(struct shm *shm, size_t size) { int saved_errno; char *path; if (size == 0) fatalx("zero size"); if (ppath( shm->name, sizeof shm->name, "%s.XXXXXXXXXX", __progname) != 0) return (NULL); if ((path = shm_path(shm)) == NULL) return (NULL); if ((shm->fd = mkstemp(path)) == -1) return (NULL); strlcpy(shm->name, xbasename(path), sizeof shm->name); if (shm_expand(shm, size) != 0) goto error; shm->data = mmap(NULL, size, SHM_PROT, SHM_FLAGS, shm->fd, 0); if (shm->data == MAP_FAILED) goto error; madvise(shm->data, size, MADV_SEQUENTIAL); shm->size = size; return (shm->data); error: saved_errno = errno; unlink(path); errno = saved_errno; return (NULL); } /* Destroy shm file. */ void shm_destroy(struct shm *shm) { char *path; if (*shm->name == '\0') return; shm_close(shm); if ((path = shm_path(shm)) == NULL) fatal("unlink failed"); if (unlink(path) != 0) fatal("unlink failed"); *shm->name = '\0'; } /* Close and unmap shm without destroying file. */ void shm_close(struct shm *shm) { if (shm->fd == -1) return; if (munmap(shm->data, shm->size) != 0) fatal("munmap failed"); shm->data = NULL; close(shm->fd); shm->fd = -1; } /* Reopen and map shm file. */ void * shm_reopen(struct shm *shm) { char *path; if ((path = shm_path(shm)) == NULL) return (NULL); if ((shm->fd = open(path, O_RDWR, 0)) == -1) return (NULL); shm->data = mmap(NULL, shm->size, SHM_PROT, SHM_FLAGS, shm->fd, 0); if (shm->data == MAP_FAILED) return (NULL); madvise(shm->data, shm->size, MADV_SEQUENTIAL); return (shm->data); } /* Set ownership of shm file. */ int shm_owner(struct shm *shm, uid_t uid, gid_t gid) { if (fchown(shm->fd, uid, gid) != 0) return (-1); return (0); } /* Resize an shm file. */ void * shm_resize(struct shm *shm, size_t nmemb, size_t size) { size_t newsize = nmemb * size; if (size == 0) fatalx("zero size"); if (SIZE_MAX / nmemb < size) fatalx("nmemb * size > SIZE_MAX"); #ifndef HAVE_MREMAP if (munmap(shm->data, shm->size) != 0) fatal("munmap failed"); shm->data = NULL; #endif if (shm_expand(shm, newsize) != 0) return (NULL); #ifdef HAVE_MREMAP shm->data = mremap(shm->data, shm->size, newsize, MREMAP_MAYMOVE); #else shm->data = mmap(NULL, newsize, SHM_PROT, SHM_FLAGS, shm->fd, 0); #endif if (shm->data == MAP_FAILED) return (NULL); madvise(shm->data, newsize, MADV_SEQUENTIAL); shm->size = newsize; return (shm->data); } fdm-1.7+cvs20140912/deliver-stdout.c0000600000175000017500000000324210657065031015321 0ustar hawkhawk/* $Id: deliver-stdout.c,v 1.8 2007/08/10 13:37:29 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #include "deliver.h" int deliver_stdout_deliver(struct deliver_ctx *, struct actitem *); void deliver_stdout_desc(struct actitem *, char *, size_t); struct deliver deliver_stdout = { "stdout", DELIVER_INCHILD, deliver_stdout_deliver, deliver_stdout_desc }; int deliver_stdout_deliver(struct deliver_ctx *dctx, unused struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; log_debug2("%s: writing to stdout", a->name); if (fwrite(m->data, m->size, 1, stdout) != 1) { log_warn("%s: fwrite", a->name); return (DELIVER_FAILURE); } fflush(stdout); return (DELIVER_SUCCESS); } void deliver_stdout_desc(unused struct actitem *ti, char *buf, size_t len) { strlcpy(buf, "stdout", len); } fdm-1.7+cvs20140912/regress/0000700000175000017500000000000012404656672013663 5ustar hawkhawkfdm-1.7+cvs20140912/regress/Makefile0000600000175000017500000000072611122277347015324 0ustar hawkhawk# $Id: Makefile,v 1.18 2008/12/17 22:36:23 nicm Exp $ CLEANFILES= *~ TESTFILES!= echo *.test TARGETS= .for n in ${TESTFILES} TARGETS+= ${n} ${n}: @echo ${n}: @HOME=. awk -v CMD="../fdm -hvvvnf /dev/stdin" \ -v DEBUG=${DEBUG} -f test.awk ${n} .endfor .MAIN: all .PHONY: regress clean ${TARGETS} .PRECIOUS: ${TARGETS} all: regress regress: ${TARGETS} cd tests && ${MAKE} cd test-pop3 && ${MAKE} cd test-imap && ${MAKE} clean: rm -f ${CLEANFILES} fdm-1.7+cvs20140912/regress/nested.test0000600000175000017500000000240110665517662016047 0ustar hawkhawk# $Id: nested.test,v 1.4 2007/08/30 10:45:06 nicm Exp $ !account "account" stdin !action "action" drop match "abc" { match "def" action "action" } @- ^added rule 0: matches=regexp "abc" in any nested$ @- ^added rule 1: matches=regexp "def" in any actions="action"$ @0 match "abc" accounts { } { match "def" action "action" } @1 ^/dev/stdin: syntax error at line .$ match "abc" and accounts { "account" } { match "def" action "action" } @- ^added rule 0: matches=regexp "abc" in any and account "account" nested$ @- ^added rule 1: matches=regexp "def" in any actions="action"$ @0 match "abc" and account "account" { match "def" action "action" } @- ^added rule 0: matches=regexp "abc" in any and account "account" nested$ @- ^added rule 1: matches=regexp "def" in any actions="action"$ @0 match "abc" { } @0 ^added rule 0: matches=regexp "abc" in any nested$ match "abc" and accounts { } { } @1 ^/dev/stdin: syntax error at line .$ match "abc" and account "account" { } @0 ^added rule 0: matches=regexp "abc" in any and account "account" nested$ match "abc" and accounts { "account" } { } @0 ^added rule 0: matches=regexp "abc" in any and account "account" nested$ match "abc" and account "account" { } @0 ^added rule 0: matches=regexp "abc" in any and account "account" nested$ fdm-1.7+cvs20140912/regress/test-imap/0000700000175000017500000000000012404656671015565 5ustar hawkhawkfdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-6.test0000600000175000017500000000037711101534323021570 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test. >) -- invalid response: Test. fdm-1.7+cvs20140912/regress/test-imap/Makefile0000600000175000017500000000057011122277347017224 0ustar hawkhawk# $Id: Makefile,v 1.2 2008/12/17 22:36:23 nicm Exp $ CLEANFILES= *~ *.fifo.{in,out} *.log *.conf TESTFILES!= echo imap-*.test TARGETS= .for n in ${TESTFILES} TARGETS+= ${n} ${n}: @HOME=. FDM="../../fdm -h" sh test-imap.sh ${n} .endfor .MAIN: all .PHONY: regress clean ${TARGETS} .PRECIOUS: ${TARGETS} all: regress regress: ${TARGETS} clean: rm -f ${CLEANFILES} fdm-1.7+cvs20140912/regress/test-imap/imap-one-1.test0000600000175000017500000000046511101534323020316 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >7 OK <8 LOGOUT >8 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-9.test0000600000175000017500000000037511101534323021571 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {0} >FLAGS ()) >5 OK -- empty message fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-8.test0000600000175000017500000000047611101534323021572 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test. FLAGS ()) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >7 OK <8 LOGOUT >8 OK fdm-1.7+cvs20140912/regress/test-imap/imap-capability-3.test0000600000175000017500000000011410703366654021670 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY -- no IMAP4rev1 capability: \* CAPABILITY fdm-1.7+cvs20140912/regress/test-imap/imap-login-1.test0000600000175000017500000000013610703367667020666 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >2 OK -- unexpected data: 2 OK fdm-1.7+cvs20140912/regress/test-imap/imap-zero-2.test0000600000175000017500000000037610703366655020540 0ustar hawkhawk>* OK test test <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK test test <2 LOGIN {4} >+ test test + test test 2 OK test test <3 SELECT {5} >+ test test * 0 EXISTS >3 OK test test <4 CLOSE >4 OK test test <5 LOGOUT >5 OK test test fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-6.test0000600000175000017500000000036010703367667021167 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 >+ test 2 OK test test <3 SELECT {5} >+ * 0 EXISTS >3 OK <4 CLOSE >4 OK <5 LOGOUT >5 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-uid-2.test0000600000175000017500000000026611101534323021405 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >4 OK -- unexpected data: 4 OK fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-3.test0000600000175000017500000000020210703367666021156 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 # One space. >+ -- invalid response: + fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-5.test0000600000175000017500000000040111101534323021553 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 NO -- unexpected data: 5 NO fdm-1.7+cvs20140912/regress/test-imap/imap-capability-1.test0000600000175000017500000000006310703366654021671 0ustar hawkhawk>* OK <1 CAPABILITY >1 OK -- unexpected data: 1 OK fdm-1.7+cvs20140912/regress/test-imap/imap-close-1.test0000600000175000017500000000047211101534323020640 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >7 NO -- unexpected data: 7 NO fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-4.test0000600000175000017500000000033611101534323021561 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >5 NO -- unexpected data: 5 NO fdm-1.7+cvs20140912/regress/test-imap/imap-logout-1.test0000600000175000017500000000051611101534323021043 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >7 OK <8 LOGOUT >8 NO -- unexpected data: 8 NO fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-3.test0000600000175000017500000000041211101534323021553 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {} -- invalid response: \* 1 UID FETCH (BODY\[\] {} fdm-1.7+cvs20140912/regress/test-imap/imap-capability-4.test0000600000175000017500000000006310703371501021657 0ustar hawkhawk>* OK <1 CAPABILITY >1 NO -- unexpected data: 1 NO fdm-1.7+cvs20140912/regress/test-imap/imap-select-5.test0000600000175000017500000000026010703371502021016 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 0 EXISTS >* 1 EXISTS >3 OK <4 CLOSE >4 OK <5 LOGOUT >5 OK fdm-1.7+cvs20140912/regress/test-imap/imap-select-3.test0000600000175000017500000000022210703371502021012 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ 3 NO -- unexpected data: 3 NO fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-uid-1.test0000600000175000017500000000026611101534323021404 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >5 OK -- unexpected data: 5 OK fdm-1.7+cvs20140912/regress/test-imap/test-imap.sh0000600000175000017500000000246510703364542020026 0ustar hawkhawk#!/bin/sh # $Id: test-imap.sh,v 1.1 2007/10/11 09:14:10 nicm Exp $ [ -z "$FDM" ] && exit 1 TEST=$1 FIFO=$TEST.fifo TYPE=imap cat <$TEST.conf set lock-file "$TEST.lock" account 'account' $TYPE pipe "cat $FIFO.in & cat >$FIFO.out" user "test" pass "test" match all action drop EOF rm -f $FIFO.in $FIFO.out mkfifo $FIFO.in $FIFO.out || exit 1 $FDM -mvvvv -f $TEST.conf f >$TEST.log 2>&1 & PID=$! cat $FIFO.out |& hold() { while kill -0 $! 2>/dev/null; do perl -e 'select(undef,undef,undef,0.01)' done } quit() { rm -f $FIFO.in $FIFO.out $TEST.conf [ "$DEBUG" = "" ] && rm -f $TEST.log if [ $1 -ne 1 ]; then echo "$TEST: PASSED" else echo "$TEST: FAILED" fi exit $1 } awk '/^\>/ { print substr($0, 2) }' $TEST >$FIFO.in || exit 1 awk '/^\/dev/null || quit 1 grep "^account: fetching error. aborted" $TEST.log >/dev/null || quit 1 quit 2 done if [ $? -eq 0 ]; then hold grep "^account: [0-9]* messages processed" $TEST.log >/dev/null || quit 1 quit 0 fi fdm-1.7+cvs20140912/regress/test-imap/imap-select-2.test0000600000175000017500000000021010703371501021005 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >3 NO -- unexpected data: 3 NO fdm-1.7+cvs20140912/regress/test-imap/imap-one-2.test0000600000175000017500000000065511101534323020320 0ustar hawkhawk>* OK test test <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK test test <2 LOGIN {4} >+ test test + test test 2 OK test test <3 SELECT {5} >+ test test * 1 EXISTS >3 OK test test <4 UID SEARCH ALL >* SEARCH 1 >4 OK test test <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK test test <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK test test <7 CLOSE >7 OK test test <8 LOGOUT >8 OK test test fdm-1.7+cvs20140912/regress/test-imap/imap-zero-1.test0000600000175000017500000000024410703366655020531 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 0 EXISTS >3 OK <4 CLOSE >4 OK <5 LOGOUT >5 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-7.test0000600000175000017500000000036411101534323021565 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {0} >) >5 OK -- empty message fdm-1.7+cvs20140912/regress/test-imap/imap-close-2.test0000600000175000017500000000047611101534323020645 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >8 OK -- unexpected data: 8 OK fdm-1.7+cvs20140912/regress/test-imap/imap-store-flags-1.test0000600000175000017500000000045711101534323021764 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 NO -- unexpected data: 6 NO fdm-1.7+cvs20140912/regress/test-imap/imap-logout-2.test0000600000175000017500000000051611101534323021044 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >6 OK <7 CLOSE >7 OK <8 LOGOUT >9 OK -- unexpected data: 9 OK fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-5.test0000600000175000017500000000034610703367667021172 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 >+ test 2 OK <3 SELECT {5} >+ * 0 EXISTS >3 OK <4 CLOSE >4 OK <5 LOGOUT >5 OK fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-2.test0000600000175000017500000000020010703367666021153 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 # No spaces. >+ -- invalid response: + fdm-1.7+cvs20140912/regress/test-imap/imap-capability-2.test0000600000175000017500000000011310703366654021666 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >2 OK -- unexpected data: 2 OK fdm-1.7+cvs20140912/regress/test-imap/imap-select-4.test0000600000175000017500000000022210703371502021013 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ 3 OK -- unexpected data: 3 OK fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-1.test0000600000175000017500000000017010703367666021160 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 >2 OK -- unexpected data: 2 OK fdm-1.7+cvs20140912/regress/test-imap/imap-store-flags-2.test0000600000175000017500000000045711101534323021765 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.) >5 OK <6 UID STORE 1 +FLAGS.SILENT (\Deleted) >7 OK -- unexpected data: 7 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-B.test0000600000175000017500000000042611101534323021577 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {6} >Test. FLAGS ()) >5 OK -- invalid response: Test. FLAGS ()) fdm-1.7+cvs20140912/regress/test-imap/imap-cram-md5-4.test0000600000175000017500000000026610703367667021172 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 >1 OK <2 AUTHENTICATE CRAM-MD5 >+ test 3 OK -- unexpected data: 3 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-1.test0000600000175000017500000000040011101534323021546 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.X) -- invalid response: Test.X) fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-A.test0000600000175000017500000000042411101534323021574 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test. FLAGS () >5 OK -- invalid response: Test. FLAGS () fdm-1.7+cvs20140912/regress/test-imap/imap-select-1.test0000600000175000017500000000021010703371501021004 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >3 OK -- unexpected data: 3 OK fdm-1.7+cvs20140912/regress/test-imap/imap-login-2.test0000600000175000017500000000015310703367667020666 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ 2 OK -- unexpected data: 2 OK fdm-1.7+cvs20140912/regress/test-imap/imap-fetch-body-2.test0000600000175000017500000000040011101534323021547 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ + 2 OK <3 SELECT {5} >+ * 1 EXISTS >3 OK <4 UID SEARCH ALL >* SEARCH 1 >4 OK <5 UID FETCH 1 BODY[] >* 1 UID FETCH (BODY[] {5} >Test.)) -- invalid response: Test.)) fdm-1.7+cvs20140912/regress/test-imap/imap-login-3.test0000600000175000017500000000015310703371501020645 0ustar hawkhawk>* OK <1 CAPABILITY >* CAPABILITY IMAP4rev1 >1 OK <2 LOGIN {4} >+ 2 NO -- unexpected data: 2 NO fdm-1.7+cvs20140912/regress/tests/0000700000175000017500000000000012404656672015025 5ustar hawkhawkfdm-1.7+cvs20140912/regress/tests/test-tag8.sh0000600000175000017500000000041710676247476017214 0ustar hawkhawk#!/bin/sh # $Id: test-tag8.sh,v 1.1 2007/09/25 18:11:10 nicm Exp $ . ./test.subr && test_init cat <$TEST.in } test_out() { cat >$TEST.out } test_run() { ( echo "set lock-file \"$TEST.lock\"" echo "account \"test\" stdin" cat echo "match all action write \"$TEST.data\"" ) >$TEST.conf chmod 600 $TEST.conf FLAGS=-q if [ "$DEBUG" != "" ]; then FLAGS=-vvv fi cat $TEST.in|$FDM $FLAGS -f $TEST.conf f || exit 1 diff -u $TEST.out $TEST.data RESULT=$? if [ $RESULT -eq 0 ]; then echo "$0: PASSED" else echo "$0: FAILED" fi if [ "$DEBUG" == "" ]; then rm -f $TEST.in $TEST.out $TEST.conf $TEST.data fi exit $RESULT } fdm-1.7+cvs20140912/regress/tests/test-macro5.sh0000600000175000017500000000037110665641270017522 0ustar hawkhawk#!/bin/sh # $Id: test-macro5.sh,v 1.1 2007/08/30 22:20:40 nicm Exp $ FDM="$FDM -D%test=1" . ./test.subr && test_init cat < # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # function failed(cmd) { failures++; print (FILENAME ":" line ": FAILED: " cmd); } function passed(cmd) { print (FILENAME ":" line ": PASSED: " cmd); } BEGIN { failures = 0; line = 0; nlines = 0; nheaders = 0; nmatches = 0; } /.*/ { line++; } /^!.+/ { headers[nheaders] = substr($0, 2); nheaders++; next; } /^[^@!\#].+/ { lines[nlines] = $0; nlines++; next; } /^@- .+/ { matches[nmatches] = substr($0, 4); nmatches++; next; } /^@[0-9]( .*)?/ { rc = int(substr($0, 2, 1)); matches[nmatches] = substr($0, 4); if (matches[nmatches] != 0 && matches[nmatches] != "") { nmatches++; } cmd = "(echo '" for (i = 0; i < nheaders; i++) { cmd = cmd headers[i] "';echo '"; } for (i = 0; i < nlines; i++) { if (i != nlines - 1) { cmd = cmd lines[i] "';echo '"; } else { cmd = cmd lines[i]; } } cmd = cmd "')|" CMD " 2>&1"; for (i = 0; i < nmatches; i++) { found[i] = 0; } do { error = cmd | getline; if (error == -1) { break; } if (DEBUG != "") { print ("\t" $0); } for (i = 0; i < nmatches; i++) { if ($0 ~ matches[i]) { found[i] = 1; } } } while (error == 1); close(cmd); if (error == -1) { failed(cmd); next; } nlines = 0; nfound = 0; for (i = 0; i < nmatches; i++) { if (found[i] == 1) { nfound++; } } if (nfound != nmatches) { nmatches = 0; failed(cmd); next; } nmatches = 0; if (system(cmd " 2>/dev/null") != rc) { failed(cmd); next; } passed(cmd); } END { exit (failures); } fdm-1.7+cvs20140912/regress/attachment.test0000600000175000017500000000263310605445475016720 0ustar hawkhawk# $Id: attachment.test,v 1.5 2007/04/06 13:29:33 nicm Exp $ !account "account" stdin !action "action" drop match attachment count < 0 action "action" @0 ^added rule 0: matches=attachment count < 0 actions="action"$ match attachment count > 0 action "action" @0 ^added rule 0: matches=attachment count > 0 actions="action"$ match attachment count == 0 action "action" @0 ^added rule 0: matches=attachment count == 0 actions="action"$ match attachment count != 0 action "action" @0 ^added rule 0: matches=attachment count != 0 actions="action"$ match attachment total-size < 0 action "action" @0 ^added rule 0: matches=attachment total-size < 0 actions="action"$ match attachment total-size > 0 action "action" @0 ^added rule 0: matches=attachment total-size > 0 actions="action"$ match attachment any-size < 0 action "action" @0 ^added rule 0: matches=attachment any-size < 0 actions="action"$ match attachment any-size > 0 action "action" @0 ^added rule 0: matches=attachment any-size > 0 actions="action"$ match attachment any-name "string" action "action" @0 ^added rule 0: matches=attachment any-name "string" actions="action"$ match attachment any-name "" action "action" @1 ^/dev/stdin: invalid string at line .$ match attachment any-type "string" action "action" @0 ^added rule 0: matches=attachment any-type "string" actions="action"$ match attachment any-type "" action "action" @1 ^/dev/stdin: invalid string at line .$ fdm-1.7+cvs20140912/regress/macros.test0000600000175000017500000000417510653324717016055 0ustar hawkhawk# $Id: macros.test,v 1.8 2007/07/30 09:05:19 nicm Exp $ %macro = 0 @0 ^added macro "%macro": 0$ %macro = 9223372036854775808 @1 ^/dev/stdin: number is too large at line .$ %macro = 9223372036854775807 @0 ^added macro "%macro": 9223372036854775807$ $macro = "" @0 ^added macro "\$macro": ""$ $macro = "string" @0 ^added macro "\$macro": "string"$ $macro = "one" $macro = "two" @- ^added macro "\$macro": "one"$ @- ^added macro "\$macro": "two"$ @0 %macro = 1000 %macro = 2000 @- ^added macro "%macro": 1000$ @- ^added macro "%macro": 2000$ @0 # ---------------------------------------------------------------------------- # UNDEFINED account $macro stdin @1 ^/dev/stdin: undefined macro: \$macro at line .$ account %macro stdin @1 ^/dev/stdin: syntax error at line .$ set maximum-size %macro @1 ^/dev/stdin: undefined macro: %macro at line .$ account "${macro}" stdin @1 ^/dev/stdin: invalid account name at line .$ account "a${macro}" stdin @0 ^added account "a": fetch=stdin$ account "${macro}b" stdin @0 ^added account "b": fetch=stdin$ account "a${macro}b" stdin @0 ^added account "ab": fetch=stdin$ account "%{macro}" stdin @1 ^/dev/stdin: invalid account name at line .$ account "a%{macro}" stdin @0 ^added account "a": fetch=stdin$ account "%{macro}b" stdin @0 ^added account "b": fetch=stdin$ account "a%{macro}b" stdin @0 ^added account "ab": fetch=stdin$ # ---------------------------------------------------------------------------- # DEFINED !%macro = 1000 !$macro = "one" account $macro stdin @0 ^added account "one": fetch=stdin$ account %macro stdin @1 ^/dev/stdin: syntax error at line .$ set maximum-size %macro @0 account "${macro}" stdin @0 ^added account "one": fetch=stdin$ account "a${macro}" stdin @0 ^added account "aone": fetch=stdin$ account "${macro}b" stdin @0 ^added account "oneb": fetch=stdin$ account "a${macro}b" stdin @0 ^added account "aoneb": fetch=stdin$ account "%{macro}" stdin @0 ^added account "1000": fetch=stdin$ account "a%{macro}" stdin @0 ^added account "a1000": fetch=stdin$ account "%{macro}b" stdin @0 ^added account "1000b": fetch=stdin$ account "a%{macro}b" stdin @0 ^added account "a1000b": fetch=stdin$ fdm-1.7+cvs20140912/regress/time.test0000600000175000017500000000573410641274613015525 0ustar hawkhawk# $Id: time.test,v 1.1 2007/06/29 21:28:43 nicm Exp $ set timeout 0 @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 @0 ^options are: .*timeout=1, set timeout 2147483648 @1 ^/dev/stdin: timeout too long: 2147483648 at line .$ set timeout 0 second @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 second @0 ^options are: .*timeout=1, set timeout 2147483648 second @1 ^/dev/stdin: timeout too long: 2147483648 at line .$ set timeout 0 seconds @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 seconds @0 ^options are: .*timeout=1, set timeout 2147483648 seconds @1 ^/dev/stdin: timeout too long: 2147483648 at line .$ set timeout 0 minute @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 minute @0 ^options are: .*timeout=60, set timeout 357913945 minute @1 ^/dev/stdin: timeout too long: 21474836700 at line .$ set timeout 0 minutes @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 minutes @0 ^options are: .*timeout=60, set timeout 357913945 minutes @1 ^/dev/stdin: timeout too long: 21474836700 at line .$ set timeout 0 hour @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 hour @0 ^options are: .*timeout=3600, set timeout 5965234 hour @1 ^/dev/stdin: timeout too long: 21474842400 at line .$ set timeout 0 hours @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 hours @0 ^options are: .*timeout=3600, set timeout 5965234 hours @1 ^/dev/stdin: timeout too long: 21474842400 at line .$ set timeout 0 day @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 day @0 ^options are: .*timeout=86400, set timeout 24856 day @1 ^/dev/stdin: timeout too long: 2147558400 at line .$ set timeout 0 days @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 days @0 ^options are: .*timeout=86400, set timeout 24856 days @1 ^/dev/stdin: timeout too long: 2147558400 at line .$ set timeout 0 week @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 week @0 ^options are: .*timeout=604800, set timeout 3551 week @1 ^/dev/stdin: timeout too long: 2147644800 at line .$ set timeout 0 weeks @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 weeks @0 ^options are: .*timeout=604800, set timeout 3551 weeks @1 ^/dev/stdin: timeout too long: 2147644800 at line .$ set timeout 0 month @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 month @1 ^/dev/stdin: timeout too long: 2419200 at line .$ set timeout 888 month @1 ^/dev/stdin: timeout too long: 2148249600 at line .$ set timeout 0 months @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 months @1 ^/dev/stdin: timeout too long: 2419200 at line .$ set timeout 888 months @1 ^/dev/stdin: timeout too long: 2148249600 at line .$ set timeout 0 year @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 year @1 ^/dev/stdin: timeout too long: 29030400 at line .$ set timeout 74 year @1 ^/dev/stdin: timeout too long: 2148249600 at line .$ set timeout 0 years @1 ^/dev/stdin: zero timeout at line .$ set timeout 1 years @1 ^/dev/stdin: timeout too long: 29030400 at line .$ set timeout 74 years @1 ^/dev/stdin: timeout too long: 2148249600 at line .$ fdm-1.7+cvs20140912/regress/account.test0000600000175000017500000002107710774521337016226 0ustar hawkhawk# $Id: account.test,v 1.8 2008/04/01 21:02:23 nicm Exp $ account "" @1 ^/dev/stdin: syntax error at line .$ # ---------------------------------------------------------------------------- # STDIN account "" stdin @1 ^/dev/stdin: invalid account name at line .$ account "name" stdin @0 ^added account "name": fetch=stdin$ # ---------------------------------------------------------------------------- # POP3 account "name" pop3 @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" @1 ^/dev/stdin: ./.netrc: No such file or directory at line .$ account "name" pop3 server "server" user "user" @1 ^/dev/stdin: ./.netrc: No such file or directory at line .$ account pop3 server "server" user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 "server" user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" user pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" user "user" "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" user "user" pass @1 ^/dev/stdin: syntax error at line .$ account "name" pop3 server "server" user "user" pass "pass" @0 ^added account "name": fetch=pop3 server "server" port pop3 user "user"$ account "name" pop3s server "server" user "user" pass "pass" @0 ^added account "name": fetch=pop3s server "server" port pop3s user "user"$ account "name" pop3 server "server" port 123 user "user" pass "pass" @0 ^added account "name": fetch=pop3 server "server" port 123 user "user"$ account "name" pop3 server "server" port "port" user "user" pass "pass" @0 ^added account "name": fetch=pop3 server "server" port port user "user"$ account "name" pop3 server "server" port 0 user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "name" pop3 server "server" port 65536 user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "name" pop3 server "server" port "" user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "" pop3 server "server" user "user" pass "pass" @1 ^/dev/stdin: invalid account name at line .$ account "name" pop3 server "" user "user" pass "pass" @1 ^/dev/stdin: invalid host at line .$ account "name" pop3 server "server" user "" pass "pass" @1 ^/dev/stdin: invalid user at line .$ account "name" pop3 server "server" user "user" pass "" @1 ^/dev/stdin: invalid pass at line .$ # ---------------------------------------------------------------------------- # IMAP account "name" imap @1 ^/dev/stdin: syntax error at line .$ account "name" imap server @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" @1 ^/dev/stdin: ./.netrc: No such file or directory at line .$ account "name" imap server "server" user "user" @1 ^/dev/stdin: ./.netrc: No such file or directory at line .$ account imap server "server" user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap "server" user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server user "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" "user" pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user pass "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user "user" "pass" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user "user" pass @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user "user" pass "pass" "folder" @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user "user" pass "pass" folder @1 ^/dev/stdin: syntax error at line .$ account "name" imap server "server" user "user" pass "pass" @0 ^added account "name": fetch=imap server "server" port imap user "user" folders "INBOX"$ account "name" imaps server "server" user "user" pass "pass" @0 ^added account "name": fetch=imaps server "server" port imaps user "user" folders "INBOX"$ account "name" imap server "server" user "user" pass "pass" folder "folder" @0 ^added account "name": fetch=imap server "server" port imap user "user" folders "folder"$ account "name" imaps server "server" user "user" pass "pass" folder "folder" @0 ^added account "name": fetch=imaps server "server" port imaps user "user" folders "folder"$ account "name" imap server "server" user "user" pass "pass" folders { "folder1" "folder2" } @0 ^added account "name": fetch=imap server "server" port imap user "user" folders "folder1" "folder2"$ account "name" imaps server "server" user "user" pass "pass" folders { "folder1" "folder2" } @0 ^added account "name": fetch=imaps server "server" port imaps user "user" folders "folder1" "folder2"$ account "name" imap server "server" port 123 user "user" pass "pass" @0 ^added account "name": fetch=imap server "server" port 123 user "user" folders "INBOX"$ account "name" imap server "server" port "port" user "user" pass "pass" @0 ^added account "name": fetch=imap server "server" port port user "user" folders "INBOX"$ account "name" imap server "server" port 0 user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "name" imap server "server" port 65536 user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "name" imap server "server" port "" user "user" pass "pass" @1 ^/dev/stdin: invalid port at line .$ account "" imap server "server" user "user" pass "pass" @1 ^/dev/stdin: invalid account name at line .$ account "name" imap server "" user "user" pass "pass" @1 ^/dev/stdin: invalid host at line .$ account "name" imap server "server" user "" pass "pass" @1 ^/dev/stdin: invalid user at line .$ account "name" imap server "server" user "user" pass "" @1 ^/dev/stdin: invalid pass at line .$ # ---------------------------------------------------------------------------- # NNTP account "name" nntp @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" group @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" group { } @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" groups @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" groups "group" @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" groups { } @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" group "group" "cache" @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" groups { "group" } cache @1 ^/dev/stdin: syntax error at line .$ account "name" nntp server "server" port 123 group "group" cache "cache" @0 ^added account "name": fetch=nntp server "server" port 123 groups "group" cache "cache"$ account "name" nntp server "server" port "port" group "group" cache "cache" @0 ^added account "name": fetch=nntp server "server" port port groups "group" cache "cache"$ account "name" nntp server "server" port 0 group "group" cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "name" nntp server "server" port 65536 group "group" cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "name" nntp server "server" port "" group "group" cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "name" nntp server "server" port 123 groups { "group" } cache "cache" @0 ^added account "name": fetch=nntp server "server" port 123 groups "group" cache "cache"$ account "name" nntp server "server" port "port" groups { "group" } cache "cache" @0 ^added account "name": fetch=nntp server "server" port port groups "group" cache "cache"$ account "name" nntp server "server" port 0 groups { "group" } cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "name" nntp server "server" port 65536 groups { "group" } cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "name" nntp server "server" port "" groups { "group" } cache "cache" @1 ^/dev/stdin: invalid port at line .$ account "" nntp server "server" group "group" cache "cache" @1 ^/dev/stdin: invalid account name at line .$ account "name" nntp server "" group "group" cache "cache" @1 ^/dev/stdin: invalid host at line .$ account "name" nntp server "server" group "" cache "cache" @1 ^/dev/stdin: invalid group at line .$ account "name" nntp server "server" group "group" cache "" @1 ^/dev/stdin: invalid cache at line .$ fdm-1.7+cvs20140912/regress/size.test0000600000175000017500000001200510641274613015526 0ustar hawkhawk# $Id: size.test,v 1.4 2007/06/29 21:28:43 nicm Exp $ set maximum-size 0 @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 @0 ^options are: .*maximum-size=1, set maximum-size 2147483648 @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0b @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1b @0 ^options are: .*maximum-size=1, set maximum-size 2147483648b @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0B @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1B @0 ^options are: .*maximum-size=1, set maximum-size 2147483648B @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 bytes @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 bytes @0 ^options are: .*maximum-size=1, set maximum-size 2147483648 bytes @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 byte @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 byte @0 ^options are: .*maximum-size=1, set maximum-size 2147483648 byte @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0K @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1K @0 ^options are: .*maximum-size=1024, set maximum-size 2097152K @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0k @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1k @0 ^options are: .*maximum-size=1024, set maximum-size 2097152k @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 kb @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 kb @0 ^options are: .*maximum-size=1024, set maximum-size 2097152 kb @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 KB @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 KB @0 ^options are: .*maximum-size=1024, set maximum-size 2097152 KB @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 kilobytes @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 kilobytes @0 ^options are: .*maximum-size=1024, set maximum-size 2097152 kilobytes @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 kilobyte @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 kilobyte @0 ^options are: .*maximum-size=1024, set maximum-size 2097152 kilobyte @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0M @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1M @0 ^options are: .*maximum-size=1048576, set maximum-size 2048M @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0m @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1m @0 ^options are: .*maximum-size=1048576, set maximum-size 2048M @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 mb @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 mb @0 ^options are: .*maximum-size=1048576, set maximum-size 2048 mb @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 MB @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 MB @0 ^options are: .*maximum-size=1048576, set maximum-size 2048 MB @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 megabytes @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 megabytes @0 ^options are: .*maximum-size=1048576, set maximum-size 2048 megabytes @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 megabyte @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 megabyte @0 ^options are: .*maximum-size=1048576, set maximum-size 2048 megabyte @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0G @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1G @0 ^options are: .*maximum-size=1073741824, set maximum-size 2G @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0g @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1g @0 ^options are: .*maximum-size=1073741824, set maximum-size 2g @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 gb @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 gb @0 ^options are: .*maximum-size=1073741824, set maximum-size 2 gb @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 GB @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 GB @0 ^options are: .*maximum-size=1073741824, set maximum-size 2 GB @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 gigabytes @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 gigabytes @0 ^options are: .*maximum-size=1073741824, set maximum-size 2 gigabytes @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ set maximum-size 0 gigabyte @1 ^/dev/stdin: zero maximum size at line .$ set maximum-size 1 gigabyte @0 ^options are: .*maximum-size=1073741824, set maximum-size 2 gigabyte @1 ^/dev/stdin: maximum size too large: 2147483648 at line .$ fdm-1.7+cvs20140912/regress/options.test0000600000175000017500000000714711101534323016247 0ustar hawkhawk# $Id: options.test,v 1.10 2008/10/28 07:01:39 nicm Exp $ set lock-types test @1 ^/dev/stdin: unknown token: test at line .$ set lock-types flock @0 ^locking using: flock $ set lock-types fcntl @0 ^locking using: fcntl $ set lock-types dotlock @0 ^locking using: dotlock $ set lock-types flock fcntl @1 ^/dev/stdin: fcntl and flock locking cannot be used together at line .$ set lock-types dotlock fcntl @0 ^locking using: fcntl dotlock $ set lock-types dotlock flock @0 ^locking using: flock dotlock $ set lock-file "" @0 set lock-file "/tmp/file" @0 set delete-oversized @0 ^options are:.*delete-oversized set allow-multiple @0 ^options are:.*allow-multiple set default-user "0" @0 ^options are:.*default-user="0" set default-user "root" @0 ^options are:.*default-user="root" set command-user "0" @0 ^options are:.*command-user="0" set command-user "root" @0 ^options are:.*command-user="root" set unmatched-mail drop @0 ^options are:.*unmatched-mail=drop set unmatched-mail keep @0 ^options are:.*unmatched-mail=keep set unmatched-mail test @1 ^/dev/stdin: unknown token: test at line .$ set file-umask 777 @0 ^options are:.*file-umask=777 set file-umask 0 @0 ^options are:.*file-umask=000 set file-umask 7777 @1 ^/dev/stdin: invalid umask: 7777 at line .$ set file-umask test @1 ^/dev/stdin: unknown token: test at line .$ set file-group 0 @0 ^options are:.*file-group=0 set file-group "wheel" @0 ^options are:.*file-group=0 set queue-high 0 @1 ^/dev/stdin: zero queue-high at line .$ set queue-high 1 @0 ^options are:.*queue-high=1, queue-low=0 set queue-high 25 @0 ^options are:.*queue-high=25, queue-low=18 set queue-high 49 @0 ^options are:.*queue-high=49, queue-low=36 set queue-high 50 @0 ^options are:.*queue-high=50, queue-low=37 set queue-high 51 @1 ^/dev/stdin: queue-high too big: 51 at line .$ set queue-low 0 @1 ^/dev/stdin: queue-high not specified at line .$ set queue-low 1 @1 ^/dev/stdin: queue-high not specified at line .$ set queue-low 25 @1 ^/dev/stdin: queue-high not specified at line .$ set queue-low 49 @1 ^/dev/stdin: queue-high not specified at line .$ set queue-low 50 @1 ^/dev/stdin: queue-high not specified at line .$ set queue-low 51 @1 ^/dev/stdin: queue-low too big: 51 at line .$ set queue-high 25 set queue-low 0 @0 ^options are:.*queue-high=25, queue-low=0 set queue-high 25 set queue-low 1 @0 ^options are:.*queue-high=25, queue-low=1 set queue-high 25 set queue-low 24 @0 ^options are:.*queue-high=25, queue-low=24 set queue-high 25 set queue-low 25 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 25 set queue-low 26 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 25 set queue-low 49 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 25 set queue-low 50 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 25 set queue-low 51 @1 ^/dev/stdin: queue-low too big: 51 at line .$ set queue-high 0 set queue-low 25 @1 ^/dev/stdin: zero queue-high at line .$ set queue-high 1 set queue-low 25 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 24 set queue-low 25 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 25 set queue-low 25 @1 ^/dev/stdin: queue-low must be smaller than queue-high at line .$ set queue-high 26 set queue-low 25 @0 ^options are:.*queue-high=26, queue-low=25 set queue-high 49 set queue-low 25 @0 ^options are:.*queue-high=49, queue-low=25 set queue-high 50 set queue-low 25 @0 ^options are:.*queue-high=50, queue-low=25 set queue-high 51 set queue-low 25 @1 ^/dev/stdin: queue-high too big: 51 at line .$ fdm-1.7+cvs20140912/regress/test-pop3/0000700000175000017500000000000012404656672015521 5ustar hawkhawkfdm-1.7+cvs20140912/regress/test-pop3/Makefile0000600000175000017500000000057011122277347017157 0ustar hawkhawk# $Id: Makefile,v 1.2 2008/12/17 22:36:23 nicm Exp $ CLEANFILES= *~ *.fifo.{in,out} *.log *.conf TESTFILES!= echo pop3-*.test TARGETS= .for n in ${TESTFILES} TARGETS+= ${n} ${n}: @HOME=. FDM="../../fdm -h" sh test-pop3.sh ${n} .endfor .MAIN: all .PHONY: regress clean ${TARGETS} .PRECIOUS: ${TARGETS} all: regress regress: ${TARGETS} clean: rm -f ${CLEANFILES} fdm-1.7+cvs20140912/regress/test-pop3/pop3-apop-3.test0000600000175000017500000000011210703356412020361 0ustar hawkhawk>+OK POP3 test> +OK +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-dele-1.test0000600000175000017500000000027510703355460020345 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 5 +OK >Test. >. -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-2.test0000600000175000017500000000016210703242163020360 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >2 uidl2 -- unexpected data: 2 uidl2 fdm-1.7+cvs20140912/regress/test-pop3/pop3-retr-2.test0000600000175000017500000000022110703355461020401 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 10 +OK >Test. >. +OK +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-apop-4.test0000600000175000017500000000014010703356412020363 0ustar hawkhawk>+OK POP3 test +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-retr-4.test0000600000175000017500000000020210703355461020402 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 0 +OK >. -- empty message fdm-1.7+cvs20140912/regress/test-pop3/pop3-pass-2.test0000600000175000017500000000012310703235526020373 0ustar hawkhawk>+OK POP3 +OK +OK test test test +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-3.test0000600000175000017500000000013610703242163020362 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >. -- invalid response: . fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-1.test0000600000175000017500000000017410703242163020362 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/test-pop3.sh0000600000175000017500000000246510703251274017711 0ustar hawkhawk#!/bin/sh # $Id: test-pop3.sh,v 1.3 2007/10/10 22:31:24 nicm Exp $ [ -z "$FDM" ] && exit 1 TEST=$1 FIFO=$TEST.fifo TYPE=pop3 cat <$TEST.conf set lock-file "$TEST.lock" account 'account' $TYPE pipe "cat $FIFO.in & cat >$FIFO.out" user "test" pass "test" match all action drop EOF rm -f $FIFO.in $FIFO.out mkfifo $FIFO.in $FIFO.out || exit 1 $FDM -mvvvv -f $TEST.conf f >$TEST.log 2>&1 & PID=$! cat $FIFO.out |& hold() { while kill -0 $! 2>/dev/null; do perl -e 'select(undef,undef,undef,0.01)' done } quit() { rm -f $FIFO.in $FIFO.out $TEST.conf [ "$DEBUG" = "" ] && rm -f $TEST.log if [ $1 -ne 1 ]; then echo "$TEST: PASSED" else echo "$TEST: FAILED" fi exit $1 } awk '/^\>/ { print substr($0, 2) }' $TEST >$FIFO.in || exit 1 awk '/^\/dev/null || quit 1 grep "^account: fetching error. aborted" $TEST.log >/dev/null || quit 1 quit 2 done if [ $? -eq 0 ]; then hold grep "^account: [0-9]* messages processed" $TEST.log >/dev/null || quit 1 quit 0 fi fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-7.test0000600000175000017500000000015610703242164020371 0ustar hawkhawk>+OK POP3 +OK +OK +OK 2 0 +OK >1 uidl1 >2 uidl1 -- UID collision: uidl1 fdm-1.7+cvs20140912/regress/test-pop3/pop3-stat-3.test0000600000175000017500000000012310703234317020376 0ustar hawkhawk>+OK POP3 +OK +OK +OK 0 0 test test test +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-pass-1.test0000600000175000017500000000014210703235525020372 0ustar hawkhawk>+OK POP3 +OK -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-5.test0000600000175000017500000000013010703242163020356 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 >. -- empty UID fdm-1.7+cvs20140912/regress/test-pop3/pop3-apop-2.test0000600000175000017500000000011210703356412020360 0ustar hawkhawk>+OK POP3 +OK +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-apop-1.test0000600000175000017500000000013410703356412020363 0ustar hawkhawk>+OK POP3 +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-list-2.test0000600000175000017500000000017510703246435020410 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 2 0 -- unexpected data: +OK 2 0 fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-4.test0000600000175000017500000000016210703243100020352 0ustar hawkhawk>+OK POP3 +OK +OK +OK 2 0 +OK >1 uidl1 >3 uidl3 -- unexpected data: 3 uidl3 fdm-1.7+cvs20140912/regress/test-pop3/pop3-user-2.test0000600000175000017500000000012310703235526020403 0ustar hawkhawk>+OK POP3 +OK test test test +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-6.test0000600000175000017500000000016310703242163020365 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK # Note two spaces here. >1 >. -- bad UID: fdm-1.7+cvs20140912/regress/test-pop3/pop3-one.test0000600000175000017500000000022010703237772020052 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 5 +OK >Test. >. +OK +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-8.test0000600000175000017500000000034110703243100020355 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK # 70 characters. >1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx >. +OK 1 5 +OK >Test. >. +OK +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-retr-1.test0000600000175000017500000000024610703355461020407 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 5 -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-uidl-9.test0000600000175000017500000000037010703243100020360 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK # 71 characters. >1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -- UID too big: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx fdm-1.7+cvs20140912/regress/test-pop3/pop3-stat-1.test0000600000175000017500000000011610703233307020374 0ustar hawkhawk>+OK POP3 +OK +OK +OK -- invalid response: +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-stat-4.test0000600000175000017500000000015510703235526020407 0ustar hawkhawk>+OK POP3 +OK +OK -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-retr-3.test0000600000175000017500000000022110703355461020402 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. +OK 1 1 +OK >Test. >. +OK +O K fdm-1.7+cvs20140912/regress/test-pop3/pop3-list-1.test0000600000175000017500000000022510703246435020403 0ustar hawkhawk>+OK POP3 +OK +OK +OK 1 0 +OK >1 uidl1 >. -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-zero.test0000600000175000017500000000010410703233311020232 0ustar hawkhawk>+OK POP3 +OK +OK +OK 0 0 +OK fdm-1.7+cvs20140912/regress/test-pop3/pop3-apop-5.test0000600000175000017500000000017210703356412020371 0ustar hawkhawk>+OK POP3 -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-user-1.test0000600000175000017500000000012210703235526020401 0ustar hawkhawk>+OK POP3 -ERR test test test -- unexpected data: -ERR test test test fdm-1.7+cvs20140912/regress/test-pop3/pop3-stat-2.test0000600000175000017500000000012210703233311020365 0ustar hawkhawk>+OK POP3 +OK +OK +OK 0 -- invalid response: +OK 0 fdm-1.7+cvs20140912/match-size.c0000600000175000017500000000331710577566340014430 0ustar hawkhawk/* $Id: match-size.c,v 1.12 2007/03/19 20:04:48 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_size_match(struct mail_ctx *, struct expritem *); void match_size_desc(struct expritem *, char *, size_t); struct match match_size = { "size", match_size_match, match_size_desc }; int match_size_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_size_data *data = ei->data; struct mail *m = mctx->mail; if (data->cmp == CMP_LT && m->size < data->size) return (MATCH_TRUE); else if (data->cmp == CMP_GT && m->size > data->size) return (MATCH_TRUE); return (MATCH_FALSE); } void match_size_desc(struct expritem *ei, char *buf, size_t len) { struct match_size_data *data = ei->data; const char *cmp = ""; if (data->cmp == CMP_LT) cmp = "<"; else if (data->cmp == CMP_GT) cmp = ">"; xsnprintf(buf, len, "size %s %zu", cmp, data->size); } fdm-1.7+cvs20140912/fetch-stdin.c0000600000175000017500000000716310663524340014565 0ustar hawkhawk/* $Id: fetch-stdin.c,v 1.68 2007/08/24 09:46:08 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" void fetch_stdin_abort(struct account *); u_int fetch_stdin_total(struct account *); void fetch_stdin_desc(struct account *, char *, size_t); int fetch_stdin_state_init(struct account *, struct fetch_ctx *); int fetch_stdin_state_mail(struct account *, struct fetch_ctx *); int fetch_stdin_state_exit(struct account *, struct fetch_ctx *); struct fetch fetch_stdin = { "stdin", fetch_stdin_state_init, NULL, NULL, fetch_stdin_abort, NULL, fetch_stdin_desc }; /* Abort; close stdin. */ void fetch_stdin_abort(unused struct account *a) { close(STDIN_FILENO); } /* Initialise stdin fetch. */ int fetch_stdin_state_init(struct account *a, struct fetch_ctx *fctx) { /* Check stdin is valid. */ if (isatty(STDIN_FILENO)) { log_warnx("%s: stdin is a tty. ignoring", a->name); return (FETCH_ERROR); } if (fcntl(STDIN_FILENO, F_GETFL) == -1) { if (errno != EBADF) fatal("fcntl failed"); log_warnx("%s: stdin is invalid", a->name); return (FETCH_ERROR); } fctx->state = fetch_stdin_state_mail; return (FETCH_AGAIN); } /* Fetch mail from stdin. */ int fetch_stdin_state_mail(struct account *a, struct fetch_ctx *fctx) { struct mail *m = fctx->mail; struct io *io; char *line, *cause; /* Open io for stdin. */ io = io_create(STDIN_FILENO, NULL, IO_LF); if (conf.debug > 3 && !conf.syslog) io->dup_fd = STDOUT_FILENO; /* Initialise the mail. */ if (mail_open(m, IO_BLOCKSIZE) != 0) { log_warn("%s: failed to create mail", a->name); goto error; } m->size = 0; /* Add default tags. */ default_tags(&m->tags, NULL); /* Loop reading the mail. */ for (;;) { /* * There can only be one mail on stdin so reentrancy is * irrelevent. This is a good thing since we want to check for * close which means end of mail. */ switch (io_pollline2(io, &line, &fctx->lbuf, &fctx->llen, conf.timeout, &cause)) { case 0: /* Normal close is fine. */ goto out; case -1: if (errno == EAGAIN) continue; log_warnx("%s: %s", a->name, cause); xfree(cause); goto error; } if (append_line(m, line, strlen(line)) != 0) { log_warn("%s: failed to resize mail", a->name); goto error; } if (m->size > conf.max_size) break; } out: if (io != NULL) io_free(io); fctx->state = fetch_stdin_state_exit; return (FETCH_MAIL); error: if (io != NULL) io_free(io); return (FETCH_ERROR); } /* Fetch finished; return exit. */ int fetch_stdin_state_exit(unused struct account *a, unused struct fetch_ctx *fctx) { close(STDIN_FILENO); return (FETCH_EXIT); } void fetch_stdin_desc(unused struct account *a, char *buf, size_t len) { strlcpy(buf, "stdin", len); } fdm-1.7+cvs20140912/CHANGES0000600000175000017500000007527011561601066013207 0ustar hawkhawk07 May 2011 * Add mbox tags for messages fetched from a mbox 01 September 2010 * Detect GMail's XYZZY capability for IMAP and use it to try and workaround some of their broken behaviour (incorrectly reported message sizes). 31 May 2009 * Print a warning on missing maildirs when fetching from them rather than crashing or giving an error. Reported by Frank Terbeck. 17 May 2009 * Introduce a configure script and tidy up build infrastructure. 02 May 2009 * GMail IMAP doesn't correctly set the \Seen flag after UID FETCH BODY[], so explicitly set it with STORE when mail is kept. Reported by Patrice Clement. 30 April 2009 * Properly count mails when polling multiple folders on a single IMAP server, reported by Claudio M. Alessi. 24 March 2009 * Support user and pass on NNTP, requested by Michael Hamann. 08 March 2009 * Escape . properly when delivering to SMTP. 27 December 2008 * Don't be as strict about format at the end of messages when using IMAP - accept additional information as well as FLAGS. Reported by rivo nurges. 22 December 2008 * fdm 1.6 released. 17 December 2008 * Only look at HOME environment variable if -h flag is passed. 28 October 2008 * Alter IMAP fetching code to build a UID list of mails to fetch and then fetch them, rather than iterating through all the mails regardless. 07 September 2008 * Process all privsep messages in parent rather than just the first. Fixes timeouts reported by Frank Terbeck. 08 August 2008 * Sort fetch list for POP3 to preserve server mailbox order, requested by Jeremy C Reed. * Fix bug where current user could overwrite default-user/command-user from configuration file. 26 June 2008 * Lookup using courier-authlib. Build with make -DLOOKUP_COURIER and use "set lookup-order courier passwd". * Framework for changing user lookup method. At the moment only normal Unix passwd (getpwnam(3)) is supported. This means only string usernames are supported - to enter a numeric Unix UID, enclose it in quotes. * Get rid of from-headers which was ugly and unreliable. 30 May 2008 * Make NNTP fetching save the cache file after each mail since some NNTP servers (such as my ISP's) are horrendously unreliable. * Handle 423/430 response from ARTICLE command in NNTP correctly. 26 May 2008 * New option: parallel-accounts. Sets number of accounts to fetch in parallel. 22 May 2008 * deliver-imap fetch method; suggested by Jason Dixon a while ago. 03 April 2008 * Support an extra space on the optional FLAGS response when fetching a mail, fixes problems with CommuniGate Pro, thanks to Anthony Wood. * New flags, no-cram-md5 and no-login, on IMAP accounts to force fdm not to use cram-md5 or login authentication even if the server supports it. From Anthony Wood. 01 April 2008 * Support SIGUSR1 as an alias for SIGINFO, for platforms without SIGINFO. 06 March 2008 * Support multiple folders (folders { "a" "b" }) on IMAP fetching. Requested by Claudio Alessi. 05 March 2008 * Print current progress on SIGINFO. Be careful with this, it is possible there are places where an unhandled EINTR would cause fdm to die. 04 March 2008 * fdm 1.5 released. 10 February 2008 * Consistently use geteuid(). * Correct mail_month/mail_dayofyear tags. 10 January 2008 * Added "from" to smtp action to specify envelope from. Requested by Marco Lopes. 07 January 2008 * Use <>s around envelope addresses when using SMTP. Reported by Marco Lopes. 08 December 2007 * Don't let mbox or maildir fetch code exit and free stuff until all the mail is dealt with (queues are empty). Fixes mbox fetching problems reported by akarinotengoku at gmail.com. 03 December 2007 * Bug fix: restore non-blocking mode properly after constructing SSL connection. This bug prevented SSL over a proxy working. 06 November 2007 * Use +FLAGS.SILENT instead of +FLAGS to mark IMAP mail as deleted, as we don't need the new flags. 03 November 2007 * Exchange sometimes includes a FLAGS update as part of its FETCH response, which made fdm complain. Change to accept it without error. Reported by Leo D'Angelo. * Google IMAP has problems with naked flag lists on STORE commands (which are valid). Change to enclose the flags list in brackets. Reported by Andrew Pantyukhin. 25 October 2007 * New option, no-create, prevents fdm creating missing path components or files when delivering to maildirs or mboxes. * Create entire paths if missing when delivering to maildirs or mboxes. 10 October 2007 * Don't accept "+OK N" as well as "+OK N M" in response to STAT. * New option: command-user. This is the user used to run exec/pipe match conditions if one is not specified. The default is the user who ran fdm (root if running as root). 09 October 2007 * Command line cache manipulation. The following commands are available: fdm cache list [path] List details of cache path or all caches in the configuration file if not specified. fdm cache dump path Dump cache keys in the form "key time" where time is is Unix time (seconds from epoch). This allows to easily sort by time: fdm cache dump ~/my/cache|sort -n +2 Or to print the time in a particular date format: fdm cache dump ~/my/cache|while read i; do set -- $i echo $1 `date -r$2 "+%Y-%m-%d% %H:%M:%S"` done And so on. fdm cache add path key fdm cache remove path key Add a key to or remove a key from a cache. fdm cache clear path Clear an entire cache (remove all the keys). This feature suggested by Thomas@BIC. * Restore regress files to distribution. Requested by Mike Erdely for the OpenBSD port. Note that there is no guarantee regressions tests will run on other operating systems! 02 October 2007 * "remove-from-cache" action. * Rename to-cache to add-to-cache (to-cache remains as a compatibility alias). This is to match the planned remove-from-cache and perhaps other similar. * The account/all deprecated support and warnings are now gone. 01 October 2007 * fdm 1.4 released. 25 September 2007 * For POP3 and IMAP, don't have a separate state for waiting-to-purge. Mail could potentially be dropped (in fact, was very likely to be) while waiting for queues to empty in that state. Instead, loop though the next state which ensures all dropped mail is dealt with before attempting to purge. * When tags are replaced in strings, shell metacharacters are now stripped. This is to permit expressions such as: match "^Subject: (.*)" action tag "subject" value "%1" match all ... action exec "echo %[subject]" Without risk of the subject being, for instance, ";rm -Rf ~". This applies to all tags, even those defined manually or by fdm itself, and happens every time any tag is inserted (so in the above, the subject is in fact stripped twice, once when inserted as %1 and once as %[subject] - the second time to no effect). The list of characters to strip can be altered using the strip-characters option. The default is: set strip-characters "\\<>$%^&*|{}[]\"'`;" Stripping may be completely disabled by setting this string to "" (this will return to the old behaviour), or disabled per-tag by prefixing the tag name with :, for example %[:subject] or %:1. Note that this protection is limited: it does not, for instance, prevent use of ".." if using tags to create paths. Care should always be taken when inserting any data extracted from an incoming mail into a shell command. 24 September 2007 * All regexps (match, string, command) are now case insensitive by default and may be prefixed by "case" to enable case sensitivity. * The match string syntax changes were bloat and are reverted. 19 September 2007 * POP3 over pipe. Same as IMAP. 18 September 2007 * Split POP3 core off into pop3-common.c a la IMAP, in preparation for POP3-over-pipe. * Change POP3 old/new mail to work from a list of mail-to-fetch and only ask for that mail, rather than retrieving and checking every mail UID. 17 September 2007 * Quote IMAP folder name to allow spaces and so on. SF bug 1791056. 05 September 2007 * Don't purge if no mails have been dropped. 03 September 2007 * Options to fetch only new or old mail from POP3 or IMAP accounts. Based on request to mailing list, and ensuing discussion, from patnel972-fdm at yahoo.fr. This introduces two new keywords for accounts: new-only and old-only. For POP3 accounts, a cache file must also be specified (similar to NNTP); this holds the list of UIDs for all the mail present in the account on the last fetch. For IMAP, the server-managed \Seen flag is used. Servers should set this flag once a mail has been read (downloaded). The \Recent flag isn't used because it is unset when any client is told about the mail - even a poll is enough. Examples of syntax are: account pop3 server ... new-only cache "~/my/pop3.cache.file" account imap server ... old-only This is useful for people who like to keep mail on the server to be read using a web interface or suchlike. Note that it is up to the user to make sure their account doesn't get full! fdm does not check this or have any abilities to delete or manage mail that isn't fetched. 02 September 2007 * More changes to io.c/command.c to fix issues on Linux introduced by fixes to IMAP-over-pipe. Hopefully all working now. 31 August 2007 * Rewrote user header searching and address matching to clean up the code and work with multiple headers, also fix some fnmatch problems. 30 August 2007 * Fixed silly bug in replace.c. Using the base rather than data offset for %0-%9 so they pointed at the wrong data if a from line had been trimmed. Found by Jo Fahlke, reported via Frank Terbeck through a Debian bug report. * Move command-line macro parsing into parse.y/parse-fn.c and make it actually work again. Found by Jo Fahlke, reported via Frank Terbeck. * account and all tokens are now normal match conditions. So, you can do useless stuff like: match all and all or all ... AND, more importantly, matching accounts now CHANGES to require an operator and follow the normal left-to-right precedence. So: match all account "a*" ... Becomes: match account "a*" ... And: match "a regexp" or age > 10 accounts { "acc1" acc2" } ... Becomes: match accounts { "acc1" "acc2 " } and "a regexp" or age > 10 ... fdm 1.4 will emit a warning and convert the old style into the new style, but in 1.5 the warning will become an error, and in 1.6 the old code will be removed entirely. So update your rulesets! Note that due to limitations in yacc, the line number of the rule in the warning may be off by a few lines (usually one line too far). 25 August 2007 * Break timer stuff off into timer.c. * Fix queue-high == 1 to not hang after first mail. 24 August 2007 * Don't even try to check for command child exit until its std{in,out,err} are closed. Hopefully fixes problems seen on Linux by Frank Terbeck. * Be more consistent about lists when parsing configuration file. * Don't try to strip duplicates from any lists instead of trying for some of them and not others. * remove-header now accepts a list of headers in {}. 23 August 2007 * Yet another fetch reorganisation to try to make it clearer. Move tracking completed mail into fetch code, move mail and state stuff outside, and simplify the API and data structures. Also change the way the fetch poll loop works and fix some potential problems with IMAP-over-pipe. Hopefully few regressions... This loses the IMAP multiple delete combining but that isn't a huge loss. 18 August 2007 * Tidy up io code a little, break some big functions and split headers stuff into dedicated io.h. Also lift fd limit. * Make SMTP delivery work again (blank lines were failing a length check in buffer_add from io_write). SF bug 1776786. 16 August 2007 * Remove header action now allows fnmatch patterns. * Fix a silly bug with removing headers. * Clarify headers/body again. The body offset is now the first byte of the body, so a mail of a single blank line has size == body == 1 (so, body size == 0 rather than 1). "\nBody" always had body == 1 and not having "\n" the same was confusing me. 15 August 2007 * A few POP3 servers supply an APOP cookie and then refuse the APOP command. Handle them with a "no-apop" flag to POP3 account statements to disable APOP. Reported by Frank Terbeck. 10 August 2007 * Combine append into write and exec into pipe even more to get rid of deliver-append.c and deliver-exec.c. * add-from on stdout delivery is gone, an acceptable from line can be prepended using rewrite. 06 August 2007 * TDB is now a mandatory dependency. 03 August 2007 * Fetching from mboxes. 02 August 2007 * Handle IMAP mails properly if they have no trailing newline. * Use APOP for POP3 or CRAM-MD5 for IMAP if offered by server. Note that fdm currently does NOT fall back to plaintext auth if the server offers APOP or CRAM-MD5 but it doesn't work. 01 August 2007 * fdm 1.3c released. * Wrong size for match buffer in pcre.c. SF.net bug 1765524. * Try to buffer as much as possible when reading, this prevents problems since SSL_read seems to buffer data itself which poll can't know about. * Fix a bug with SSL where a "don't stop" error (want read/want write) would make fdm think there was data received when there wasn't, resulting in spurious new lines and fetching being aborted. This somehow failed to show up in the two months since 1.1 was released. Reported by Steven Mestdagh. 31 July 2007 * Accept only ".\r\n" as terminator for POP3 and NNTP, not a '.' followed by anything except '.'. * Don't include a leading space in the POP3 UID. 30 July 2007 * fdm 1.3a and 1.3b released. * Change to using setitimer for SSL_connect timeout. OpenBSD, and possibly others, limit SO_RCVTIMEO to 327 seconds. Also try smaller timeouts if setting the first one fails, to a minimum of 30 seconds. Reported by Steven Mestdagh. * fdm 1.3 released. 25 July 2007 * Tidied and refactored much of file.c and deliver-{mbox,maildir}.c. * Brought back fatal and fatalx as #defines which prepend __func__ and pass through to log_fatal/log_fatalx. 24 July 2007 * Embedding an undefined macro in a string with ${} is no longer an error, it is just treated as empty. This makes ifdef/endif more useful. 17 July 2007 * Only store shared filename (NAME_MAX) in struct shm and construct path into temp dir on-the-fly. Saves (PATH_MAX - NAME_MAX) per struct. * Trailing newlines are now stripped from message_id tag (as they used to be). This makes any existing caches by message-id useless. * Use SO_RCVTIMEO to apply configured timeout when connecting to SSL servers (SSL_connect must block as it is the easiest way to ensure the certificate is received before checking its validity). 16 July 2007 * Outright reject configuration files that use caches unless built with -DDB, rather than waiting until the cache is used. * Rewrite the malloc debugging stuff: lose the call counting, use a splay tree instead of a fixed array and use __builtin_return_address and direct calls from xmalloc.c rather than horrible #define games, even if it means the ultimate output is less useful. 15 July 2007 * Permit each account to start multiple delivery child processes simultaneously (up to the maximum queue length). This doesn't help much in normal cases but with particularly lengthy but non-CPU-intensive delivery actions (try exec "sleep 1" ;-P), it can help a lot. * Introduce some randomness into the lock sleep delay when waiting for mboxes. 14 July 2007 * Use mremap(2) on Linux. 13 July 2007 * Fix stupid bug (grr) in strb.c which caused a segfault if realloc moved the buffer when it was being expanded; also clean up macros a bit when here. 11 July 2007 * Add %[mail_rfc822date] which contains the mail date and time (or current date and time if invalid) in RFC822 format and %[rfc822date] which is the same for the current date/time. I probably need to stop adding tags now $() works. * Additional example from Giorgio Lando (examples/g-lando.conf). * Add two-digit year (year2/mail_year2) and hostname tags. * Simplify fetching: line counting and body detection is now done globally for all fetch types in the mail queuing code, as is checking for empty and oversize mails. * There is now no concept of a body-less mail (m->body == -1). Mails with no separating blank line are assumed to be entirely body (m->body == 0), otherwise the body is the first byte after the separator. * Fix a long-standing bug in openlock (mail.c): if open(2) failed, the lock file was not removed. * NNTPS fetching, suggested by Maximilian Gass. * After rethinking, remove some useless fclose error checks. 08 July 2007 * Check permissions on include files as well as main configuration file. * First new parser bug: include files should be subject to tag replacement, noticed by Frank Terbeck. 05 July 2007 * Move to rewritten log.c which which makes the syslog/stderr hacks I had made to the old version less fugly. 02 July 2007 * If a mail has no body, insert new headers at the start instead of the end. This matches formail's behaviour. Requested by Giorgio Lando. Note there is no change to the regexp "in body" behaviour, although perhaps there should be. * Sort out the localtime(3)/strftime(3) mess by actually reading the man page and time.h. This means July is now the seventh rather than sixth month. Reported by Giorgio Lando. 29 June 2007 * Split parse helper functions out into parse-fn.c and do lots of other cleanup of parse.y and lex.c. * New lexer makes ifdef/ifndef/endif possible, although not completely elegant. Syntax is: ifdef $macro .... endif. Note that stuff inside ifdefs must still be valid syntax! * Scrap use of lex in favour of custom C lexer. This allows (yet another) workaround for include files, to make stuff like this work: $host = $(hostname) include "file.${host}" * Combine multiple IMAP deletions together into one command where possible, although I doubt it makes any actual difference. 28 June 2007 * Shell commands may now be called when parsing the ruleset using $() and %() operators: %year = %(date +%y) $arch = $(uname -m) These are executed at parse time. Sections between the brackets within "s are subject to escape and macro replacement as normal; parts outside quotes or inside 's are not. For example: $SHELL = "test" $cmd = $(echo "${SHELL} $SHELL \${SHELL}") Takes advantage of the fact that fdm requires {}s and the shell does not to yield: added macro "$cmd": "test /bin/ksh /bin/ksh" As with macros, commands may be used at any point a string or number is appropriate. * The mail time tags (mail_year, mail_quarter, etc) now use underscores in their names rather than dashes, for consistency. * Deliver append-string is no more, it is trivially replaced using rewrite. * Built-in cache using TDB (http://sourceforge.net/projects/tdb/). Currently disabled by default, enabled by building with make -DDB. Used as followed; # Declare the cache. $db = "~/.fdm.db" cache $db expire 2 weeks match not string "%[message_id]" to "" { # Filter messages already in the cache. match in-cache $db key "%[message_id]" action mbox "duplicate-mail" # Add message-ids to the cache (any other string can be added # but message-ids are most useful). match all action to-cache $db key "%[message_id]" continue } Note the guard statement - the key can't be empty and news (NNTP) messages don't have a message-id. 27 June 2007 * New tags taken from the date header corresponding to the current time tags: mail_hour, mail_year, etc. If the header is invalid, the current time is used. Request/idea from Maximilian Gass. * Make fdm-sanitize also obfuscate the lengths of login/pass with IMAP. * Bug fix: "and not" rather than "not and" in ruleset -vv output. * "match tag ..." is now completely gone. * "value" keyword is now preferred for add-header action: action "ah" add-header "My-Header" value "ABC" but is not mandatory. * fdm 1.2 released. 08 June 2007 * Check CN against FQDN and user-supplied host when verifying SSL certificates. 21 May 2007 * New layout for most of the fetch code. Now pretty much everything is done using a state machine for each fetch type rather than a mixture of linear code and state machine. Mail is now enqueued/dequeued using callbacks rather than returning status codes. 16 May 2007 * SHM_SYSV is no more. 10 May 2007 * Fix typo in manual and use DESTDIR in *akefile, from Frank Terbeck as a result of Debian package creation. 09 May 2007 * Code tidying in io.c and command.c. 08 May 2007 * Couple of fixes for stupid things in ARRAY_* macros, one of which was hiding a minor bug. Don't know what I was thinking when I wrote them. 04 May 2007 * "match tag ..." is now not supported. * Permit actions to call other actions: action "one" ... action "two" ... action "three" { ... actions { "one" "two" } } A maximum of five levels is permitted, spoiling the fun of: action "x" { action "x" } 30 April 2007 * Optionally verify SSL certificates. This can be turned on with the "verify-certificates" option and disabled per account by appending a "no-verify" flag to the definition. * Option to use PCRE (build with make -DPCRE). 19 April 2007 * Count the number of messages properly when there is no done function (such as for NNTP). 10 April 2007 * Install into bin rather than sbin when using GNUmakefile. From Frank Terbeck. 06 April 2007 * fdm 1.1 released. 04 April 2007 * Set the socket non-blocking in before setting up SSL in connect.c, otherwise SSL_connect might block. 03 April 2007 * Instead of doing complicated things to see if a mmap'd region is okay, just fill it with zeros using write(2). This also avoids FS fragmentation on FreeBSD, per its mmap(2). Also use MAP_NOSYNC. 30 March 2007 * Allow plural and singular to be interchanged in most places, don't insist on "accounts { "abc" }" and "account "abc"". 29 March 2007 * Tags are now done using an action (tag) rather than a special-case match rule. The old way still works but generates a warning which will become an error within a few releases. * Because yacc needs to move back and forwards, just swapping the file out from under it when including can cause problems. So, switch to letting lex do all the work and feed the include file to yacc as if it was inline. This sucks a bit but there aren't many other options. Reported by Frank Terbeck. * Lambda actions: match "regexp" action mbox ... match "regexp" actions { rewrite ... mbox ... } 28 March 2007 * Compound actions, using a list in {}s, eg: action "abc" { rewrite ... mbox ... } * Macros shouldn't be expanded inside single quotes. Fixed. * + may be used to concatenate strings. * Allow short-circuit evaluation of 'and' and 'or' operators. 27 March 2007 * Accept size with either CRLF or LF as when warning about bad size predictions from POP3 servers. Google gives the size with LF and then sends the mail data (as it must do) with CRLF. 26 March 2007 * Fix stupid use of struct strings for uid_ts and make users be printed when printing rules, actions, accounts. * Option to use SYSV shared memory (build with make -DSHM_SYSV). Not a real option, or documented, yet because I'm not sure how to deal with cleaning up on SIGTERM. It might even go away again. * Play some games to ensure that that the full extent of the mmap'd temporary file is actually writable before using it. Otherwise if there is insufficient disk space we risk getting an unexpected SIGBUS later when trying to use it. * When sending delivery done messages, check that the child process isn't dead before trying to talk to it. 25 March 2007 * Handle unlink(2)/fclose(3) failures, led to discovery of a missing fflush before fsync and a double-unlink of a tmp file (bad!), also fixed. Part of bug 1687830 from Markus Elfring. Also fix some closedir(3) return value checks. I'm still on the fence about close(2). SUSv3 says that it can only die on EBADF, EINTR and EIO: the first will cause problems before close, and I don't see how the latter can affect anything given that write(2) (or fsync(2) when applicable) will have previously returned success (fdm already checks their return). OTOH, it would be both tidy and future-proof to check it. 22 March 2007 * Return total number of mails from start function if possible and print "X of Y" when reporting mail fetches with -v. 21 March 2007 * queue-low and queue-high options. Default queue limit is 2. * Default maximum-size now 32 MB and maximum now 1 GB. 20 March 2007 * -q option to quiet unnecessary chatter. * If user/pass is not specified on IMAP or POP3 accounts, attempt to read it from the ~/.netrc file. * Redo child fetch stuff so that exec/pipe matches actually works * Stop exec/pipe matches early if only checking for regexp and it is found. * Sprinkle a few fsyncs, and block SIGTERM while writing to mboxes. 18 March 2007 * Informational messages now go to stdout rather than stderr. 16 March 2007 * Change so that the child uses poll(2) on all the fds rather than letting the fetch-* stuff poll and periodically checking the parent fd. * Make delivery children be handled in the main loop (fdm.c) along with the fetch children. This means that different accounts will not block each other when delivering. I tried allowing multiple simultaneous deliveries for each account but it doesn't work very well, particularly regarding errors. 15 March 2007 * Seriously reduce -v output in favour of -vv and -vvv. * Save mail in a queue as it is fetched and perform matching/delivery on it while waiting for the server to send the next mail. This can give a 20-50% speed increase with some rulesets, particularly with slow POP3 servers. 13 March 2007 * Don't pass stdin to poll(3) instead of closed sockets. Fixes problems with rewrite and empty mails on Linux. Reported & tested by Frank Terbeck. 12 March 2007 * The timeout setting is now used for commands too. * Expand leading ~s into home directories where appropriate. * Section headings are now numbered in MANUAL. 11 March 2007 * You can't use a va_list twice even when it's passed in as an argument. Duh. Fixes segfaults on PPC. Reported by Daniel Wilmer. 08 March 2007 * Try to introduce some consistency into tag replacement in strings. Introduce a new type (struct replstr) which indicates a string that should be replaced before use, and try to make sure replacement happens possibly for all strings where replacement should happen on parse. Hopefully this will make it easier to handle replacement properly in future too. 07 March 2007 * Handle POLLHUP properly in io.c: if it is set and POLLIN isn't set, the fd has closed. This makes stdin delivery work properly on Linux 2.4. 06 March 2007 * Submatches with commands are now added as tags command0 to command9. * pipe actions now use the command.c framework, which means their stderr is logged and stdout discarded. * mail_file tag added by maildir delivery and mbox_file by mbox. Also pass the tags block up and down between parent and child so these tags actually stick. * A new exec action, similar to pipe but stdin is /dev/null. This is useful in combination with the mail_file and mbox_file tags. 04 March 2007 * Bug fix: fill in the correct size for maildir fetched mail rather than using the rounded-up size and adding a ton of nulls. * -D on the command line overrides macro definitions in the configuration file. * Build the attachment list when the first attachment match rule is found rather than for every mail regardless. * Add a message_id tag to each mail. 03 March 2007 * add-header action and lines, body_lines, header_lines tags for IMAP/POP3, suggested by Frank Terbeck. * Linux dirname(3)/basename(3) screws up the buffer. Fixed it thanks to Frank Terbeck. * Fixed bugs in string parsing thanks to Frank Terbeck. 02 March 2007 * Made add_tag use varargs and added a server_uid tag to POP3/IMAP messages. * Added infrastructure to maintain a cache of variable-length strings within a single contiguous block of data (strb.c), so it can be passed easily up to the parent process. Use this for storing mail tags to get rid of the arbitrary tag length limits. 28 February 2007 * Man page cleanups, from Tamas TEVESZ. 27 February 2007 * fdm 1.0 released. 22 February 2007 * Fixed silly typo in NNTP code check. SF bug 1650701. * Included account name in received header. * Added stdout delivery. * New append-string delivery to add data to a mail. 19 February 2007 * Added a remove-header action to remove all instances of a header. Based on a a request by Tobias Ulmer. 09 February 2007 * Reworked the substitution stuff. Each mail now carries a list of tag name, value pairs, some of which are autogenerated (account, action, etc) and others which may be added by the user. They may be inserted in strings using %[name]. The autogenerated tags are aliased to the old single letter tokens. Based on a question from AJ Weber. * Added a timeout option to limit the time for which servers are waited. 08 February 2007 * Added file-umask and file-group options to control permissions and group ownership of created mboxes and maildirs. Altered the permissions checks to take this into account, and extended them to maildirs. Suggestion from AJ Weber. * Made mbox/maildir creation clearer in documentation. 07 February 2007 * Added -D command line option to define a macro. 05 February 2007 * Made options be printed with -v. 27 January 2007 * Fixed attachment parsing so that it works as intended. 26 January 2007 * Started a changelog. * Assigned a number to each rule and changed logging to refer to it. Also tweaked the -vv configuration file output. * Changed some vasprintf to xvasprintf now that the latter exists. * Fixed command.c to trim CR line end if present, this makes IMAP over ssh work. (0.9a) * Fixed miscalculation of length in find_header. (0.9a) * Fixed find_header to locate the first rather than second instance. * Added FQDN and what it resolves to to the domains list by default. * Added a basic Received header with local host and fdm version, may be disabled with the new set no-received option. * Free mmap'd region before mmap'ing it again in shm_realloc, otherwise the actual disk space isn't freed. This affected previous versions too but the mail was very rarely reallocated, now that the header is added it is. * Changed all the descriptions to static buffers and audited use of snprintf. $Id: CHANGES,v 1.214 2011/05/08 20:51:02 nicm Exp $ LocalWords: Terbeck authlib DLOOKUP ISP's Alessi dayofyear Lopes gmail STAT LocalWords: akarinotengoku D'Angelo Pantyukhin BIC Erdely Rf patnel Fahlke vv LocalWords: acc std nBody Mestdagh RCVTIMEO fatalx func shm dir DDB strb rfc LocalWords: Lando openlock Gass formail's ids ABC CN SYSV akefile DPCRE abc LocalWords: mmap'd NOSYNC ts DSHM Elfring SUSv netrc vvv va Wilmer replstr AJ LocalWords: TEVESZ Ulmer mmap'ing Exp sanitize fdm-1.7+cvs20140912/io.h0000600000175000017500000001010411047077052012756 0ustar hawkhawk/* $Id: io.h,v 1.4 2008/08/08 17:11:06 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef IO_H #define IO_H /* Buffer macros. */ #define BUFFER_USED(b) ((b)->size) #define BUFFER_FREE(b) ((b)->space - (b)->off - (b)->size) #define BUFFER_IN(b) ((b)->base + (b)->off + (b)->size) #define BUFFER_OUT(b) ((b)->base + (b)->off) /* Buffer structure. */ struct buffer { u_char *base; /* buffer start */ size_t space; /* total size of buffer */ size_t size; /* size of data in buffer */ size_t off; /* offset of data in buffer */ }; /* Limits at which to fail. */ #define IO_MAXLINELEN (1024 * 1024) /* 1 MB */ /* IO line endings. */ #define IO_CRLF "\r\n" #define IO_CR "\r" #define IO_LF "\n" /* Initial block size of buffer and minimum amount to try to read. */ #define IO_BLOCKSIZE 16384 #define IO_WATERMARK 12288 /* Initial line buffer length. */ #define IO_LINESIZE 256 /* Amount to poll after in io_update. */ #define IO_FLUSHSIZE (2 * IO_BLOCKSIZE) /* IO macros. */ #define IO_ROUND(n) (((n / IO_BLOCKSIZE) + 1) * IO_BLOCKSIZE) #define IO_CLOSED(io) ((io)->flags & IOF_CLOSED) #define IO_ERROR(io) ((io)->error) #define IO_RDSIZE(io) (BUFFER_USED((io)->rd)) #define IO_WRSIZE(io) (BUFFER_USED((io)->wr)) /* IO structure. */ struct io { int fd; int dup_fd; /* duplicate all data to this fd */ SSL *ssl; char *error; int flags; #define IOF_NEEDFILL 0x1 #define IOF_NEEDPUSH 0x2 #define IOF_CLOSED 0x4 #define IOF_MUSTWR 0x8 struct buffer *rd; struct buffer *wr; char *lbuf; /* line buffer */ size_t llen; /* line buffer size */ const char *eol; }; /* List of ios. */ ARRAY_DECL(iolist, struct io *); /* buffer.c */ struct buffer *buffer_create(size_t); void buffer_destroy(struct buffer *); void buffer_clear(struct buffer *); void buffer_ensure(struct buffer *, size_t); void buffer_add(struct buffer *, size_t); void buffer_reverse_add(struct buffer *, size_t); void buffer_remove(struct buffer *, size_t); void buffer_reverse_remove(struct buffer *, size_t); void buffer_insert_range(struct buffer *, size_t, size_t); void buffer_delete_range(struct buffer *, size_t, size_t); void buffer_write(struct buffer *, const void *, size_t); void buffer_read(struct buffer *, void *, size_t); void buffer_write8(struct buffer *, uint8_t); void buffer_write16(struct buffer *, uint16_t); uint8_t buffer_read8(struct buffer *); uint16_t buffer_read16(struct buffer *); /* io.c */ struct io *io_create(int, SSL *, const char *); void io_readonly(struct io *); void io_writeonly(struct io *); void io_free(struct io *); void io_close(struct io *); int io_polln(struct io **, u_int, struct io **, int, char **); int io_poll(struct io *, int, char **); int io_read2(struct io *, void *, size_t); void *io_read(struct io *, size_t); void io_write(struct io *, const void *, size_t); char *io_readline2(struct io *, char **, size_t *); char *io_readline(struct io *); void printflike2 io_writeline(struct io *, const char *, ...); void io_vwriteline(struct io *, const char *, va_list); int io_pollline2(struct io *, char **, char **, size_t *, int, char **); int io_pollline(struct io *, char **, int, char **); int io_flush(struct io *, int, char **); int io_wait(struct io *, size_t, int, char **); int io_update(struct io *, int, char **); #endif /* IO_H */ fdm-1.7+cvs20140912/connect.c0000600000175000017500000003540712277775757014037 0ustar hawkhawk/* $Id: connect.c,v 1.81 2014/02/15 23:44:47 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fdm.h" char *check_alt_names(char *, char *, X509 *); int sslverify(struct server *, SSL *, char **); int getport(char *); int httpproxy(struct server *, struct proxy *, struct io *, int, char **); int socks5proxy(struct server *, struct proxy *, struct io *, int, char **); SSL *makessl(struct server *, int, int, int, char **); char * sslerror(const char *fn) { char *cause; xasprintf(&cause, "%s: %s", fn, ERR_error_string(ERR_get_error(), NULL)); return (cause); } char * sslerror2(int n, const char *fn) { char *cause; switch (n) { case SSL_ERROR_ZERO_RETURN: errno = ECONNRESET; /* FALLTHROUGH */ case SSL_ERROR_SYSCALL: xasprintf(&cause, "%s: %s", fn, strerror(errno)); return (cause); case SSL_ERROR_WANT_CONNECT: xasprintf(&cause, "%s: timed out or need connect", fn); return (cause); case SSL_ERROR_WANT_ACCEPT: xasprintf(&cause, "%s: timed out or need accept", fn); return (cause); case SSL_ERROR_WANT_READ: xasprintf(&cause, "%s: timed out or need read", fn); return (cause); case SSL_ERROR_WANT_WRITE: xasprintf(&cause, "%s: timed out or need write", fn); return (cause); } xasprintf(&cause, "%s: %d: %s", fn, n, ERR_error_string(ERR_get_error(), NULL)); return (cause); } char * check_alt_names(char *host, char *fqdn, X509 *x509) { char *found, *buf; const GENERAL_NAMES *ans; const GENERAL_NAME *p; int n; ans = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL); if (ans == NULL) return (NULL); n = sk_GENERAL_NAME_num(ans); found = NULL; while (n-- > 0 && found == NULL) { p = sk_GENERAL_NAME_value(ans, n); if (p == NULL || p->type != GEN_DNS) continue; if (ASN1_STRING_to_UTF8((u_char **)&buf, p->d.dNSName) <= 0) continue; if (fnmatch(buf, host, FNM_NOESCAPE|FNM_CASEFOLD) == 0 || (fqdn != NULL && fnmatch(buf, fqdn, FNM_NOESCAPE|FNM_CASEFOLD) == 0)) found = buf; OPENSSL_free(buf); } sk_GENERAL_NAME_free(ans); return (found); } int sslverify(struct server *srv, SSL *ssl, char **cause) { X509 *x509; int error; char *fqdn, name[256], *ptr, *ptr2; const char *s; if ((x509 = SSL_get_peer_certificate(ssl)) == NULL) { /* No certificate, error since we wanted to verify it. */ s = "no certificate"; goto error; } /* Verify certificate. */ if ((error = SSL_get_verify_result(ssl)) != X509_V_OK) { s = X509_verify_cert_error_string(error); goto error; } /* Get certificate name. */ X509_NAME_oneline(X509_get_subject_name(x509), name, sizeof name); /* Check for CN field. */ if ((ptr = strstr(name, "/CN=")) == NULL) { s = "CN missing"; goto error; } /* Verify CN field. */ getaddrs(srv->host, &fqdn, NULL); do { ptr += 4; ptr2 = strchr(ptr, '/'); if (ptr2 != NULL) *ptr2 = '\0'; /* Compare against both given host and FQDN. */ if (fnmatch(ptr, srv->host, FNM_NOESCAPE|FNM_CASEFOLD) == 0 || (fqdn != NULL && fnmatch(ptr, fqdn, FNM_NOESCAPE|FNM_CASEFOLD)) == 0) break; if (ptr2 != NULL) *ptr2 = '/'; } while ((ptr = strstr(ptr, "/CN=")) != NULL); /* No valid CN found. Try alternative names. */ if (ptr == NULL) ptr = check_alt_names(srv->host, fqdn, x509); if (fqdn != NULL) xfree(fqdn); /* No valid CN found. */ if (ptr == NULL) { s = "no matching CN"; goto error; } /* Valid CN found. */ X509_free(x509); return (0); error: xasprintf(cause, "certificate verification failed: %s", s); if (x509 != NULL) X509_free(x509); return (-1); } void getaddrs(const char *host, char **fqdn, char **addr) { char ni[NI_MAXHOST]; struct addrinfo *ai; if (fqdn != NULL) *fqdn = NULL; if (addr != NULL) *addr = NULL; if (getaddrinfo(host, NULL, NULL, &ai) != 0) return; if (addr != NULL && getnameinfo(ai->ai_addr, ai->ai_addrlen, ni, sizeof ni, NULL, 0, NI_NUMERICHOST) == 0) xasprintf(addr, "%s", ni); if (fqdn != NULL && getnameinfo(ai->ai_addr, ai->ai_addrlen, ni, sizeof ni, NULL, 0, NI_NAMEREQD) == 0) *fqdn = xstrdup(ni); freeaddrinfo(ai); } struct proxy * getproxy(const char *xurl) { struct proxy *pr = NULL; char *ptr, *end, *saved, *url; struct { const char *proto; enum proxytype type; int ssl; const char *port; } *proxyent, proxylist[] = { { "http://", PROXY_HTTP, 0, "http" }, { "https://", PROXY_HTTPS, 1, "https" }, { "socks://", PROXY_SOCKS5, 0, "socks" }, { "socks5://", PROXY_SOCKS5, 0, "socks" }, { NULL, 0, 0, NULL } }; /* Copy the url so we can mangle it. */ saved = url = xstrdup(xurl); /* Find proxy. */ for (proxyent = proxylist; proxyent->proto != NULL; proxyent++) { if (strncmp(url, proxyent->proto, strlen(proxyent->proto)) == 0) break; } if (proxyent->proto == NULL) goto error; url += strlen(proxyent->proto); pr = xcalloc(1, sizeof *pr); pr->type = proxyent->type; pr->server.ssl = proxyent->ssl; pr->server.port = xstrdup(proxyent->port); /* Strip trailing '/' characters. */ ptr = url + strlen(url) - 1; while (ptr >= url && *ptr == '/') *ptr-- = '\0'; if (*url == '\0') goto error; /* Look for a user/pass. */ if ((end = strchr(url, '@')) != NULL) { ptr = strchr(url, ':'); if (ptr == NULL || ptr >= end) goto error; *ptr++ = '\0'; pr->user = xstrdup(url); *end++ = '\0'; pr->pass = xstrdup(ptr); if (*pr->user == '\0' || *pr->pass == '\0') goto error; url = end; } /* Extract port if available. */ if ((ptr = strchr(url, ':')) != NULL) { xfree(pr->server.port); pr->server.port = NULL; *ptr++ = '\0'; if (*ptr == '\0') goto error; pr->server.port = xstrdup(ptr); } /* And fill in the host. */ if (*url == '\0') goto error; pr->server.host = xstrdup(url); xfree(saved); return (pr); error: if (pr != NULL) { if (pr->user != NULL) xfree(pr->user); if (pr->pass != NULL) xfree(pr->pass); if (pr->server.port != NULL) xfree(pr->server.port); if (pr->server.host != NULL) xfree(pr->server.host); xfree(pr); } xfree(saved); return (NULL); } struct io * connectproxy(struct server *srv, int verify, struct proxy *pr, const char *eol, int timeout, char **cause) { struct io *io; if (pr == NULL) return (connectio(srv, verify, eol, timeout, cause)); io = connectio(&pr->server, verify, IO_CRLF, timeout, cause); if (io == NULL) return (NULL); switch (pr->type) { case PROXY_HTTP: if (httpproxy(srv, pr, io, timeout, cause) != 0) goto error; break; case PROXY_SOCKS5: if (socks5proxy(srv, pr, io, timeout, cause) != 0) goto error; break; default: fatalx("unknown proxy type"); } /* If the original request was for SSL, initiate it now. */ if (srv->ssl) { io->ssl = makessl( srv, io->fd, verify && srv->verify, timeout, cause); if (io->ssl == NULL) goto error; } io->eol = eol; return (io); error: io_close(io); io_free(io); return (NULL); } int getport(char *port) { struct servent *sv; int n; const char *errstr; sv = getservbyname(port, "tcp"); if (sv == NULL) { n = strtonum(port, 1, UINT16_MAX, &errstr); if (errstr != NULL) { endservent(); return (-1); } } else n = ntohs(sv->s_port); endservent(); return (n); } int socks5proxy(struct server *srv, struct proxy *pr, struct io *io, int timeout, char **cause) { int port, auth; char buf[1024], *ptr; size_t len; if ((port = getport(srv->port)) < 0) { xasprintf(cause, "bad port: %s", srv->port); return (-1); } /* Method selection. */ auth = pr->user != NULL && pr->pass != NULL; buf[0] = 5; buf[1] = auth ? 2 : 1; buf[2] = 0; /* 0 = no auth */ buf[3] = 2; /* 2 = user/pass auth */ io_write(io, buf, auth ? 4 : 3); if (io_wait(io, 2, timeout, cause) != 0) return (-1); io_read2(io, buf, 2); if (buf[0] != 5) { xasprintf(cause, "bad protocol version: %d", buf[0]); return (-1); } if ((buf[1] != 0 && buf[1] != 2) || (auth == 0 && buf[1] == 2)) { xasprintf(cause, "unexpected method: %d", buf[1]); return (-1); } /* User/pass negotiation. */ if (buf[1] == 2) { ptr = buf; *ptr++ = 5; len = strlen(pr->user); if (len > 255) { xasprintf(cause, "user too long"); return (-1); } *ptr++ = len; memcpy(ptr, pr->user, len); ptr += len; len = strlen(pr->pass); if (len > 255) { xasprintf(cause, "pass too long"); return (-1); } *ptr++ = len; memcpy(ptr, pr->pass, len); ptr += len; io_write(io, buf, ptr - buf); if (io_wait(io, 2, timeout, cause) != 0) return (-1); io_read2(io, buf, 2); if (buf[0] != 5) { xasprintf(cause, "bad protocol version: %d", buf[0]); return (-1); } if (buf[1] != 0) { xasprintf(cause, "authentication failed"); return (-1); } } /* Connect request. */ ptr = buf; *ptr++ = 5; *ptr++ = 1; /* 1 = connect */ *ptr++ = 0; /* reserved */ *ptr++ = 3; /* 3 = domain name */ len = strlen(srv->host); if (len > 255) { xasprintf(cause, "host too long"); return (-1); } *ptr++ = len; memcpy(ptr, srv->host, len); ptr += len; *ptr++ = (port >> 8) & 0xff; *ptr++ = port & 0xff; io_write(io, buf, ptr - buf); /* Connect response. */ if (io_wait(io, 5, timeout, cause) != 0) return (-1); io_read2(io, buf, 5); if (buf[0] != 5) { xasprintf(cause, "bad protocol version: %d", buf[0]); return (-1); } switch (buf[1]) { case 0: break; case 1: xasprintf(cause, "%d: server failure", buf[1]); return (-1); case 2: xasprintf(cause, "%d: connection not permitted", buf[1]); return (-1); case 3: xasprintf(cause, "%d: network unreachable", buf[1]); return (-1); case 4: xasprintf(cause, "%d: host unreachable", buf[1]); return (-1); case 5: xasprintf(cause, "%d: connection refused", buf[1]); return (-1); case 6: xasprintf(cause, "%d: TTL expired", buf[1]); return (-1); case 7: xasprintf(cause, "%d: command not supported", buf[1]); return (-1); case 8: xasprintf(cause, "%d: address type not supported", buf[1]); return (-1); default: xasprintf(cause, "%d: unknown failure", buf[1]); return (-1); } /* Flush the rest. */ switch (buf[3]) { case 1: /* IPv4 */ len = 5; break; case 3: /* IPv6 */ len = 17; break; case 4: /* host */ len = buf[4] + 2; break; default: xasprintf(cause, "unknown address type: %d", buf[3]); return (-1); } if (io_wait(io, len, timeout, cause) != 0) return (-1); io_read2(io, buf, len); return (0); } int httpproxy(struct server *srv, struct proxy *pr, struct io *io, int timeout, char **cause) { char *line; int port, header; if (pr->user != NULL || pr->pass != NULL) { xasprintf(cause, "HTTP proxy authentication is not supported"); return (-1); } if ((port = getport(srv->port)) < 0) { xasprintf(cause, "bad port: %s", srv->port); return (-1); } io_writeline(io, "CONNECT %s:%d HTTP/1.1", srv->host, port); io_writeline(io, NULL); header = 0; for (;;) { if (io_pollline(io, &line, timeout, cause) != 1) return (-1); if (header == 0) { if (strlen(line) < 12 || strncmp(line, "HTTP/", 5) != 0 || strncmp(line + 8, " 200", 4) != 0) { xfree(line); xasprintf(cause, "unexpected data: %s", line); return (-1); } header = 1; } else { if (*line == '\0') return (0); } xfree(line); } } SSL * makessl(struct server *srv, int fd, int verify, int timeout, char **cause) { SSL_CTX *ctx; SSL *ssl; int n, mode; ctx = SSL_CTX_new(SSLv23_client_method()); if (srv->tls1) SSL_CTX_set_options(ctx, SSL_OP_ALL); else SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_TLSv1); SSL_CTX_set_default_verify_paths(ctx); SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); ssl = SSL_new(ctx); if (ssl == NULL) { *cause = sslerror("SSL_new"); goto error; } if (SSL_set_fd(ssl, fd) != 1) { *cause = sslerror("SSL_set_fd"); goto error; } /* * Switch the socket to blocking mode to be sure we have received the * certificate. */ if ((mode = fcntl(fd, F_GETFL)) == -1) fatal("fcntl failed"); if (fcntl(fd, F_SETFL, mode & ~O_NONBLOCK) == -1) fatal("fcntl failed"); /* Set the timeout. */ timer_set(timeout / 1000); /* Connect with SSL. */ SSL_set_connect_state(ssl); if ((n = SSL_connect(ssl)) < 1) { timer_cancel(); if (timer_expired()) { xasprintf( cause, "SSL_connect: %s", strerror(ETIMEDOUT)); goto error; } *cause = sslerror2(SSL_get_error(ssl, n), "SSL_connect"); goto error; } /* Reset non-blocking mode. */ if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) fatal("fcntl failed"); /* Clear the timeout. */ timer_cancel(); /* Verify certificate. */ if (verify && sslverify(srv, ssl, cause) != 0) goto error; return (ssl); error: SSL_CTX_free(ctx); if (ssl != NULL) SSL_free(ssl); return (NULL); } struct io * connectio( struct server *srv, int verify, const char *eol, int timeout, char **cause) { int fd = -1, error = 0; struct addrinfo hints; struct addrinfo *ai; const char *fn = NULL; SSL *ssl; if (srv->ai == NULL) { memset(&hints, 0, sizeof hints); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(srv->host, srv->port, &hints, &srv->ai); if (error != 0) { *cause = xstrdup(gai_strerror(error)); return (NULL); } } for (ai = srv->ai; ai != NULL; ai = ai->ai_next) { retry: fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) { fn = "socket"; continue; } if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { error = errno; close(fd); errno = error; if (errno == EINTR) goto retry; fd = -1; fn = "connect"; continue; } break; } if (fd < 0) { xasprintf(cause, "%s: %s", fn, strerror(errno)); return (NULL); } if (!srv->ssl) return (io_create(fd, NULL, eol)); ssl = makessl(srv, fd, verify && srv->verify, timeout, cause); if (ssl == NULL) { close(fd); return (NULL); } return (io_create(fd, ssl, eol)); } fdm-1.7+cvs20140912/db-tdb.c0000600000175000017500000000663111644572335013517 0ustar hawkhawk/* $Id: db-tdb.c,v 1.14 2011/10/10 13:36:29 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #ifndef _PUBLIC_ #define _PUBLIC_ #endif #include #include "fdm.h" int db_print_item(TDB_CONTEXT *, TDB_DATA, TDB_DATA, void *); int db_expire_item(TDB_CONTEXT *, TDB_DATA, TDB_DATA, void *); int db_clear_item(TDB_CONTEXT *, TDB_DATA, TDB_DATA, void *); TDB_CONTEXT * db_open(char *path) { TDB_CONTEXT *db; #ifndef DB_UNSAFE db = tdb_open(path, 0, 0, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); #else db = tdb_open(path, 0, TDB_NOLOCK, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); #endif return (db); } void db_close(TDB_CONTEXT *db) { tdb_close(db); } int db_add(TDB_CONTEXT *db, char *k) { TDB_DATA key, value; struct cacheitem v; uint64_t tim; memset(&v, 0, sizeof v); tim = time(NULL); v.tim = htole64(tim); key.dptr = k; key.dsize = strlen(k); value.dptr = (char *) &v; value.dsize = sizeof v; return (tdb_store(db, key, value, TDB_REPLACE)); } int db_remove(TDB_CONTEXT *db, char *k) { TDB_DATA key; key.dptr = k; key.dsize = strlen(k); return (tdb_delete(db, key)); } int db_contains(TDB_CONTEXT *db, char *k) { TDB_DATA key; key.dptr = k; key.dsize = strlen(k); return (tdb_exists(db, key)); } int db_size(TDB_CONTEXT *db) { return (tdb_traverse(db, NULL, NULL)); } int db_print_item( unused TDB_CONTEXT *tdb, unused TDB_DATA key, TDB_DATA value, void *ptr) { void (*p)(const char *, ...) = ptr; struct cacheitem v; uint64_t tim; if (value.dsize != sizeof v) return (-1); memcpy(&v, value.dptr, sizeof v); tim = letoh64(v.tim); p("%.*s %llu", key.dsize, key.dptr, (unsigned long long) tim); return (0); } int db_print(TDB_CONTEXT *db, void (*p)(const char *, ...)) { if (tdb_traverse(db, db_print_item, p) == -1) return (-1); return (0); } int db_expire_item(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA value, void *ptr) { uint64_t *lim = ptr; struct cacheitem v; if (value.dsize != sizeof v) return (-1); memcpy(&v, value.dptr, sizeof v); if (letoh64(v.tim) < *lim) return (tdb_delete(tdb, key)); return (0); } int db_expire(TDB_CONTEXT *db, uint64_t age) { uint64_t lim; lim = time(NULL); if (lim <= age) return (0); lim -= age; if (tdb_traverse(db, db_expire_item, &lim) == -1) return (-1); return (0); } int db_clear_item(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA value, unused void *ptr) { if (value.dsize != sizeof (struct cacheitem)) return (-1); return (tdb_delete(tdb, key)); } int db_clear(TDB_CONTEXT *db) { if (tdb_traverse(db, db_clear_item, NULL) == -1) return (-1); return (0); } fdm-1.7+cvs20140912/lookup-courier.c0000600000175000017500000000357011204061551015322 0ustar hawkhawk/* $Id: lookup-courier.c,v 1.3 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef LOOKUP_COURIER #include #include #include #include "fdm.h" /* * The mandatory callback in this API is stupid. */ int courier_callback(struct authinfo *, void *); struct userdata *courier_udata; int courier_callback(struct authinfo *ai, unused void *data) { struct passwd *pw; courier_udata = xmalloc(sizeof *courier_udata); courier_udata->name = xstrdup(ai->address); courier_udata->home = xstrdup(ai->homedir); if (ai->sysusername != NULL) { if ((pw = getpwnam(ai->sysusername)) == NULL) { xfree(courier_udata); courier_udata = NULL; return (0); } courier_udata->uid = pw->pw_uid; courier_udata->gid = pw->pw_gid; endpwent(); } else { courier_udata->uid = *ai->sysuserid; courier_udata->gid = ai->sysgroupid; } return (0); } struct userdata * courier_lookup(const char *user) { courier_udata = NULL; if (auth_getuserinfo(__progname, user, courier_callback, NULL) != 0) return (NULL); return (courier_udata); } #endif /* LOOKUP_COURIER */ fdm-1.7+cvs20140912/child-fetch.c0000600000175000017500000004207311643331173014525 0ustar hawkhawk/* $Id: child-fetch.c,v 1.75 2011/10/06 13:51:55 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" #include "fetch.h" #include "match.h" void fetch_status(struct account *, double); int fetch_account(struct account *, struct io *, int, double); int fetch_match(struct account *, struct msg *, struct msgbuf *); int fetch_deliver(struct account *, struct msg *, struct msgbuf *); int fetch_poll(struct account *, struct iolist *, struct io *, int); int fetch_purge(struct account *); void fetch_free(void); void fetch_free1(struct mail_ctx *); int fetch_enqueue(struct account *, struct io *, struct mail *); int fetch_dequeue(struct account *, struct mail_ctx *); #ifdef DEBUG double fetch_time_polling = 0.0; double fetch_time_blocked = 0.0; #endif struct mail_queue fetch_matchq; struct mail_queue fetch_deliverq; u_int fetch_dropped; u_int fetch_kept; u_int fetch_queued; /* number of mails queued */ u_int fetch_blocked; /* blocked for parent */ int open_cache(struct account *a, struct cache *cache) { int n; if (cache->db != NULL) return (0); if ((cache->db = db_open(cache->path)) == NULL) { log_warn("%s: %s", a->name, cache->path); return (-1); } n = db_size(cache->db); log_debug3("%s: opened cache %s: %d keys", a->name, cache->path, n); if (cache->expire == 0) return (0); if (db_expire(cache->db, cache->expire) != 0) { log_warnx("%s: %s: expiry failed", a->name, cache->path); return (-1); } n -= db_size(cache->db); if (n < 0) n = 0; log_debug3("%s: cache %s: expired %d keys", a->name, cache->path, n); return (0); } int child_fetch(struct child *child, struct io *pio) { struct child_fetch_data *data = child->data; enum fdmop op = data->op; struct account *a = data->account; struct msg msg; int error, flags; double tim; #ifdef DEBUG xmalloc_clear(); COUNTFDS(a->name); #endif log_debug2("%s: fetch started, pid %ld", a->name, (long) getpid()); #ifdef HAVE_SETPROCTITLE setproctitle("child: %s", a->name); #endif log_debug2("%s: user is %lu", a->name, (u_long) geteuid()); tim = get_time(); /* Process fetch or poll. */ log_debug2("%s: started processing", a->name); flags = 0; if (op == FDMOP_POLL) flags |= FETCH_POLL; error = fetch_account(a, pio, flags, tim); log_debug2("%s: finished processing. exiting", a->name); memset(&msg, 0, sizeof msg); msg.type = MSG_EXIT; log_debug3("%s: sending exit message to parent", a->name); if (privsep_send(pio, &msg, NULL) != 0) fatalx("privsep_send error"); do { log_debug3("%s: waiting for exit message from parent", a->name); if (privsep_recv(pio, &msg, NULL) != 0) fatalx("privsep_recv error"); } while (msg.type != MSG_EXIT); #ifdef DEBUG COUNTFDS(a->name); xmalloc_report(getpid(), a->name); #endif return (error); } int fetch_poll(struct account *a, struct iolist *iol, struct io *pio, int timeout) { struct io *rio; char *cause; double tim; log_debug3( "%s: polling: %u, timeout=%d", a->name, ARRAY_LENGTH(iol), timeout); tim = get_time(); switch (io_polln( ARRAY_DATA(iol), ARRAY_LENGTH(iol), &rio, timeout, &cause)) { case 0: if (rio == pio) fatalx("parent socket closed"); log_warnx("%s: connection closed", a->name); return (-1); case -1: if (errno == EAGAIN) break; if (rio == pio) fatalx("parent socket error"); log_warnx("%s: %s", a->name, cause); xfree(cause); return (-1); } tim = get_time() - tim; #ifdef DEBUG fetch_time_polling += tim; if (fetch_blocked == fetch_queued && fetch_queued != 0) fetch_time_blocked += tim; #endif return (0); } int fetch_match(struct account *a, struct msg *msg, struct msgbuf *msgbuf) { struct mail_ctx *mctx, *this; if (TAILQ_EMPTY(&fetch_matchq)) return (0); mctx = TAILQ_FIRST(&fetch_matchq); while (mctx != NULL) { this = mctx; mctx = TAILQ_NEXT(this, entry); log_debug3("%s: " "trying (match) message %u", a->name, this->mail->idx); switch (mail_match(this, msg, msgbuf)) { case MAIL_ERROR: log_debug3("%s: match" " message %u, error", a->name, this->mail->idx); return (-1); case MAIL_DELIVER: log_debug3("%s: match" " message %u, deliver", a->name, this->mail->idx); TAILQ_REMOVE(&fetch_matchq, this, entry); TAILQ_INSERT_TAIL(&fetch_deliverq, this, entry); break; case MAIL_DONE: log_debug3("%s: match" " message %u, done", a->name, this->mail->idx); if (fetch_dequeue(a, this) != 0) return (-1); break; case MAIL_BLOCKED: log_debug3("%s: match" " message %u, blocked", a->name, this->mail->idx); fetch_blocked++; break; } } return (0); } int fetch_deliver(struct account *a, struct msg *msg, struct msgbuf *msgbuf) { struct mail_ctx *mctx, *this; if (TAILQ_EMPTY(&fetch_deliverq)) return (0); mctx = TAILQ_FIRST(&fetch_deliverq); while (mctx != NULL) { this = mctx; mctx = TAILQ_NEXT(this, entry); log_debug3("%s:" " trying (deliver) message %u", a->name, this->mail->idx); switch (mail_deliver(this, msg, msgbuf)) { case MAIL_ERROR: log_debug3("%s: deliver" " message %u, error", a->name, this->mail->idx); return (-1); case MAIL_MATCH: log_debug3("%s: deliver" " message %u, match", a->name, this->mail->idx); TAILQ_REMOVE(&fetch_deliverq, this, entry); TAILQ_INSERT_TAIL(&fetch_matchq, this, entry); break; case MAIL_BLOCKED: log_debug3("%s: deliver" " message %u, blocked", a->name, this->mail->idx); fetch_blocked++; break; } } return (0); } void fetch_free1(struct mail_ctx *mctx) { struct deliver_ctx *dctx; while (!TAILQ_EMPTY(&mctx->dqueue)) { dctx = TAILQ_FIRST(&mctx->dqueue); TAILQ_REMOVE(&mctx->dqueue, dctx, entry); user_free(dctx->udata); xfree(dctx); } ARRAY_FREE(&mctx->stack); mail_destroy(mctx->mail); xfree(mctx->mail); xfree(mctx); } void fetch_free(void) { struct mail_ctx *mctx; while (!TAILQ_EMPTY(&fetch_matchq)) { mctx = TAILQ_FIRST(&fetch_matchq); TAILQ_REMOVE(&fetch_matchq, mctx, entry); fetch_free1(mctx); } while (!TAILQ_EMPTY(&fetch_deliverq)) { mctx = TAILQ_FIRST(&fetch_deliverq); TAILQ_REMOVE(&fetch_deliverq, mctx, entry); fetch_free1(mctx); } } int fetch_purge(struct account *a) { static u_int last_total = 0, last_dropped = 0; u_int n; if (conf.purge_after == 0) return (0); n = fetch_dropped + fetch_kept; if (n == last_total || n % conf.purge_after != 0) return (0); last_total = n; if (last_dropped == fetch_dropped) { log_debug("%s: not purging, no mails dropped", a->name); return (0); } last_dropped = fetch_dropped; log_debug("%s: purging after %u mails", a->name, n); return (1); } void fetch_status(struct account *a, double tim) { u_int n; tim = get_time() - tim; n = fetch_dropped + fetch_kept; if (n > 0) { log_info("%s: %u messages processed (%u kept) in %.3f seconds " "(average %.3f)", a->name, n, fetch_kept, tim, tim / n); } else { log_info("%s: 0 messages processed in %.3f seconds", a->name, tim); } #ifdef DEBUG log_debug("%s: polled for %.3f seconds (%.3f blocked)", a->name, fetch_time_polling, fetch_time_blocked); #endif } int fetch_account(struct account *a, struct io *pio, int nflags, double tim) { struct msg msg, *msgp; struct msgbuf msgbuf; struct fetch_ctx fctx; struct cache *cache; struct iolist iol; int aborted, complete, holding, timeout; log_debug2("%s: fetching", a->name); TAILQ_INIT(&fetch_matchq); TAILQ_INIT(&fetch_deliverq); fetch_queued = fetch_dropped = fetch_kept = 0; if (nflags & FETCH_POLL && a->fetch->total == NULL) { log_info("%s: polling not supported", a->name); return (0); } fctx.llen = IO_LINESIZE; fctx.lbuf = xmalloc(fctx.llen); fctx.flags = nflags; fctx.mail = xcalloc(1, sizeof *fctx.mail); fctx.state = a->fetch->first; ARRAY_INIT(&iol); aborted = complete = holding = 0; for (;;) { log_debug3("%s: fetch loop start", a->name); if (sigusr1) { log_debug("%s: caught SIGUSR1", a->name); if (!(nflags & FETCH_POLL)) fetch_status(a, tim); sigusr1 = 0; } fetch_blocked = 0; /* Check for new privsep messages. */ msgp = NULL; if (privsep_check(pio)) { if (privsep_recv(pio, &msg, &msgbuf) != 0) fatalx("privsep_recv error"); log_debug3("%s: got message type %d, id %u", a->name, msg.type, msg.id); msgp = &msg; } /* Match and deliver mail. */ if (fetch_match(a, msgp, &msgbuf) != 0) goto abort; if (fetch_deliver(a, msgp, &msgbuf) != 0) goto abort; /* Check for purge and set flag if necessary. */ if (fetch_purge(a)) fctx.flags |= FETCH_PURGE; /* Update the holding flag. */ if (fetch_queued <= (u_int) conf.queue_low) holding = 0; if (fetch_queued >= (u_int) conf.queue_high) holding = 1; /* If not holding and not finished, call the fetch handler. */ if (!holding && !complete) { /* * Set the empty flag if queues are empty. Purging * shouldn't happen if this is clear. */ fctx.flags &= ~FETCH_EMPTY; if (fetch_queued == 0) fctx.flags |= FETCH_EMPTY; /* Call the fetch function. */ log_debug3("%s: calling fetch state (%p, flags 0x%02x)", a->name, fctx.state, fctx.flags); switch (fctx.state(a, &fctx)) { case FETCH_ERROR: /* Fetch error. */ log_debug3("%s: fetch, error", a->name); goto abort; case FETCH_EXIT: /* Fetch completed. */ log_debug3("%s: fetch, exit", a->name); complete = 1; break; case FETCH_AGAIN: /* Fetch again - no blocking. */ log_debug3("%s: fetch, again", a->name); continue; case FETCH_BLOCK: /* Fetch again - allow blocking. */ log_debug3("%s: fetch, block", a->name); break; case FETCH_MAIL: /* Mail ready. */ log_debug3("%s: fetch, mail", a->name); if (fetch_enqueue(a, pio, fctx.mail) != 0) goto abort; fctx.mail = xcalloc(1, sizeof *fctx.mail); continue; default: fatalx("unexpected fetch return"); } } /* If fetch finished and no more mails queued, exit. */ if (complete && fetch_queued == 0) goto finished; /* Prepare for poll. */ ARRAY_CLEAR(&iol); ARRAY_ADD(&iol, pio); if (a->fetch->fill != NULL) a->fetch->fill(a, &iol); /* * Work out timeout. If the queues are empty, we can block, * unless this fetch type doesn't have any sockets to poll - * then we would block forever. Otherwise, if the queues are * non-empty, we can block unless there are mails that aren't * blocked (these mails can continue to be processed). */ timeout = conf.timeout; if (fetch_queued == 0 && ARRAY_LENGTH(&iol) == 1) timeout = 0; else if (fetch_queued != 0 && fetch_blocked != fetch_queued) timeout = 0; /* Poll for fetch data or privsep messages. */ log_debug3("%s: queued %u; blocked %u; flags 0x%02x", a->name, fetch_queued, fetch_blocked, fctx.flags); if (fetch_poll(a, &iol, pio, timeout) != 0) goto abort; } abort: a->fetch->abort(a); if (nflags & FETCH_POLL) log_warnx("%s: polling error. aborted", a->name); else log_warnx("%s: fetching error. aborted", a->name); aborted = 1; finished: if (fctx.mail != NULL) { mail_destroy(fctx.mail); xfree(fctx.mail); } xfree(fctx.lbuf); fetch_free(); ARRAY_FREE(&iol); /* Close caches. */ TAILQ_FOREACH(cache, &conf.caches, entry) { if (cache->db != NULL) db_close(cache->db); } /* Print results. */ if (nflags & FETCH_POLL) log_info("%s: %u messages found", a->name, a->fetch->total(a)); else fetch_status(a, tim); return (aborted); } /* * Check mail for various problems, add headers and fill tags, then create an * mctx and enqueue it onto the fetch queue. */ int fetch_enqueue(struct account *a, struct io *pio, struct mail *m) { struct mail_ctx *mctx; char *hdr, rtime[128], *rhost, total[16]; u_int n, b; size_t size; int error; struct tm *tm; time_t t; /* * Check for oversize mails. This must be first since there is no * guarantee anything other than size is valid if oversize. */ if (m->size > conf.max_size) { log_warnx("%s: message too big: %zu bytes", a->name, m->size); if (!conf.del_big) return (-1); /* Delete the mail. */ m->decision = DECISION_DROP; if (a->fetch->commit != NULL && a->fetch->commit(a, m) == FETCH_ERROR) return (-1); mail_destroy(m); xfree(m); return (0); } /* * Find the mail body (needed by trim_from). This is probably slower * than doing it during fetching but it guarantees consistency. */ m->body = find_body(m); /* Trim "From" line, if any. */ trim_from(m); /* Check for empty mails. */ if (m->size == 0) { log_warnx("%s: empty message", a->name); return (-1); } /* Fill in standard mail attributes. */ m->decision = DECISION_DROP; m->idx = ++a->idx; m->tim = get_time(); /* Add account name tag. */ add_tag(&m->tags, "account", "%s", a->name); /* Add mail time tags. */ if (mailtime(m, &t) != 0) { log_debug2("%s: bad date header, using current time", a->name); t = time(NULL); } if ((tm = localtime(&t)) != NULL) { add_tag(&m->tags, "mail_hour", "%.2d", tm->tm_hour); add_tag(&m->tags, "mail_minute", "%.2d", tm->tm_min); add_tag(&m->tags, "mail_second", "%.2d", tm->tm_sec); add_tag(&m->tags, "mail_day", "%.2d", tm->tm_mday); add_tag(&m->tags, "mail_month", "%.2d", tm->tm_mon + 1); add_tag(&m->tags, "mail_year", "%.4d", 1900 + tm->tm_year); add_tag(&m->tags, "mail_year2", "%.2d", tm->tm_year % 100); add_tag(&m->tags, "mail_dayofweek", "%d", tm->tm_wday); add_tag(&m->tags, "mail_dayofyear", "%.2d", tm->tm_yday + 1); add_tag(&m->tags, "mail_quarter", "%d", tm->tm_mon / 3 + 1); } if (rfc822time(t, rtime, sizeof rtime) != NULL) add_tag(&m->tags, "mail_rfc822date", "%s", rtime); /* Fill in lines tags. */ count_lines(m, &n, &b); log_debug2("%s: found %u lines, %u in body", a->name, n, b); add_tag(&m->tags, "lines", "%u", n); add_tag(&m->tags, "body_lines", "%u", b); if (n - b != 0) b++; /* don't include the separator */ add_tag(&m->tags, "header_lines", "%u", n - b); /* Insert message-id tag. */ hdr = find_header(m, "message-id", &size, 1); if (hdr == NULL || size == 0 || size > INT_MAX) log_debug2("%s: message-id not found", a->name); else { log_debug2("%s: message-id is: %.*s", a->name, (int) size, hdr); add_tag(&m->tags, "message_id", "%.*s", (int) size, hdr); } /* * Insert received header. * * No header line must exceed 998 bytes. Limiting the user-supplied * stuff to 900 bytes gives plenty of space for the other stuff, and if * it gets truncated, who cares? */ if (!conf.no_received) { error = 1; if (rfc822time(time(NULL), rtime, sizeof rtime) != NULL) { rhost = conf.host_fqdn; if (rhost == NULL) rhost = conf.host_name; error = insert_header(m, "received", "Received: by " "%.450s (%s " BUILD ", account \"%.450s\");\n\t%s", rhost, __progname, a->name, rtime); } if (error != 0) log_debug3("%s: couldn't add received header", a->name); } /* Fill wrapped line list. */ n = fill_wrapped(m); log_debug2("%s: found %u wrapped lines", a->name, n); /* Create the mctx. */ mctx = xcalloc(1, sizeof *mctx); mctx->account = a; mctx->io = pio; mctx->mail = m; mctx->msgid = 0; mctx->done = 0; mctx->matched = 0; mctx->rule = TAILQ_FIRST(&conf.rules); TAILQ_INIT(&mctx->dqueue); ARRAY_INIT(&mctx->stack); /* And enqueue it. */ TAILQ_INSERT_TAIL(&fetch_matchq, mctx, entry); fetch_queued++; *total = '\0'; if (a->fetch->total != NULL && a->fetch->total(a) != 0) xsnprintf(total, sizeof total, " of %u", a->fetch->total(a)); log_debug("%s: got message %u%s: size %zu, body %zu", a->name, m->idx, total, m->size, m->body); return (0); } /* Resolve final decision and dequeue mail. */ int fetch_dequeue(struct account *a, struct mail_ctx *mctx) { struct mail *m = mctx->mail; if (conf.keep_all || a->keep) m->decision = DECISION_KEEP; switch (m->decision) { case DECISION_DROP: fetch_dropped++; log_debug("%s: deleting message %u", a->name, m->idx); break; case DECISION_KEEP: fetch_kept++; log_debug("%s: keeping message %u", a->name, m->idx); break; default: fatalx("invalid decision"); } if (a->fetch->commit != NULL && a->fetch->commit(a, m) == FETCH_ERROR) return (-1); TAILQ_REMOVE(&fetch_matchq, mctx, entry); fetch_queued--; fetch_free1(mctx); return (0); } fdm-1.7+cvs20140912/match-attachment.c0000600000175000017500000001135610664611653015603 0ustar hawkhawk/* $Id: match-attachment.c,v 1.27 2007/08/27 18:10:51 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_attachment_match(struct mail_ctx *, struct expritem *); void match_attachment_desc(struct expritem *, char *, size_t); struct match match_attachment = { "attachment", match_attachment_match, match_attachment_desc }; int match_attachment_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_attachment_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; struct attach *at; size_t size; u_int n; char *value = NULL; if (!m->attach_built) { /* Fill attachments. */ m->attach = attach_build(m); if (m->attach != NULL) attach_log(m->attach, "%s: attachment", a->name); else log_debug3("%s: no attachments", a->name); m->attach_built = 1; } if (data->op == ATTACHOP_COUNT || data->op == ATTACHOP_TOTALSIZE) { size = 0; n = 0; at = m->attach; while (at != NULL) { size += at->size; n++; at = attach_visit(at, NULL); } switch (data->op) { case ATTACHOP_COUNT: switch (data->cmp) { case CMP_EQ: if (n == data->value.num) return (MATCH_TRUE); return (MATCH_FALSE); case CMP_NE: if (n != data->value.num) return (MATCH_TRUE); return (MATCH_FALSE); case CMP_LT: if (n < data->value.num) return (MATCH_TRUE); return (MATCH_FALSE); case CMP_GT: if (n > data->value.num) return (MATCH_TRUE); return (MATCH_FALSE); default: return (MATCH_ERROR); } case ATTACHOP_TOTALSIZE: switch (data->cmp) { case CMP_LT: if (size < data->value.size) return (MATCH_TRUE); return (MATCH_FALSE); case CMP_GT: if (size > data->value.size) return (MATCH_TRUE); return (MATCH_FALSE); default: return (MATCH_ERROR); } default: return (MATCH_ERROR); } } /* If no attachments, none of the following conditions are true. */ if (m->attach == NULL) return (MATCH_FALSE); /* For any type or name matches, construct the value. */ if (data->op == ATTACHOP_ANYTYPE || data->op == ATTACHOP_ANYNAME) value = replacestr(&data->value.str, m->tags, m, &m->rml); at = m->attach; while (at != NULL) { switch (data->op) { case ATTACHOP_ANYSIZE: switch (data->cmp) { case CMP_LT: if (at->size < data->value.size) return (MATCH_TRUE); break; case CMP_GT: if (at->size > data->value.size) return (MATCH_TRUE); break; default: return (MATCH_ERROR); } break; case ATTACHOP_ANYTYPE: if (at->type == NULL) break; if (fnmatch(value, at->type, FNM_CASEFOLD) == 0) { xfree(value); return (MATCH_TRUE); } break; case ATTACHOP_ANYNAME: if (at->name == NULL) break; if (fnmatch(value, at->name, FNM_CASEFOLD) == 0) { xfree(value); return (MATCH_TRUE); } break; default: return (MATCH_ERROR); } at = attach_visit(at, NULL); } if (value != NULL) xfree(value); return (MATCH_FALSE); } void match_attachment_desc(struct expritem *ei, char *buf, size_t len) { struct match_attachment_data *data = ei->data; const char *cmp = ""; if (data->cmp == CMP_LT) cmp = "<"; else if (data->cmp == CMP_GT) cmp = ">"; else if (data->cmp == CMP_EQ) cmp = "=="; else if (data->cmp == CMP_NE) cmp = "!="; switch (data->op) { case ATTACHOP_COUNT: xsnprintf(buf, len, "attachment count %s %lld", cmp, data->value.num); break; case ATTACHOP_TOTALSIZE: xsnprintf(buf, len, "attachment total-size %s %lld", cmp, data->value.num); break; case ATTACHOP_ANYSIZE: xsnprintf(buf, len, "attachment any-size %s %lld", cmp, data->value.num); break; case ATTACHOP_ANYTYPE: xsnprintf(buf, len, "attachment any-type \"%s\"", data->value.str.str); break; case ATTACHOP_ANYNAME: xsnprintf(buf, len, "attachment any-name \"%s\"", data->value.str.str); break; default: if (len > 0) *buf = '\0'; break; } } fdm-1.7+cvs20140912/configure0000700000175000017500000000426611204173751014115 0ustar hawkhawk#!/bin/sh # $Id: configure,v 1.4 2009/05/18 05:53:45 nicm Exp $ FDM_PLATFORM=${FDM_PLATFORM:-`uname -s`} CONFIG_H=config.h echo "/* $FDM_PLATFORM */" >|$CONFIG_H CONFIG_MK=config.mk echo "# $FDM_PLATFORM" >|$CONFIG_MK case $FDM_PLATFORM in # ------------------------------------------------------------------------------ OpenBSD) cat <>$CONFIG_H #define HAVE_QUEUE_H #define HAVE_SETPROCTITLE #define HAVE_SETRESGID #define HAVE_SETRESUID #define HAVE_STRLCAT #define HAVE_STRLCPY #define HAVE_STRTONUM #define HAVE_TREE_H EOF cat <>$CONFIG_MK EOF ;; # ------------------------------------------------------------------------------ Linux) cat <>$CONFIG_H #define HAVE_SETRESUID #define HAVE_SETRESGID #define HAVE_MREMAP EOF cat <>$CONFIG_MK SRCS+= compat/strlcat.c \ compat/strlcpy.c \ compat/strtonum.c CFLAGS+= -std=c99 -D_GNU_SOURCE -D_POSIX_SOURCE `getconf LFS_CFLAGS` LIBS+= -lresolv EOF ;; # ------------------------------------------------------------------------------ Darwin) cat <>$CONFIG_H #define HAVE_QUEUE_H #define HAVE_STRLCAT #define HAVE_STRLCPY EOF cat <>$CONFIG_MK CPPFLAGS+= -I/usr/local/include/openssl \ -I/opt/local/include \ -I/sw/include LDFLAGS+= -L/opt/local/lib \ -L/sw/lib LIBS+= -lresolv -lcrypto SRCS+= compat/strtonum.c EOF ;; # ------------------------------------------------------------------------------ FreeBSD|DragonFly) cat <>$CONFIG_H #define HAVE_QUEUE_H #define HAVE_SETPROCTITLE #define HAVE_SETRESGID #define HAVE_SETRESUID #define HAVE_STRLCAT #define HAVE_STRLCPY #define HAVE_STRTONUM #define HAVE_TREE_H EOF cat <>$CONFIG_MK CPPFLAGS+= -I/usr/include/openssl EOF ;; # ------------------------------------------------------------------------------ NetBSD) cat <>$CONFIG_H #define HAVE_QUEUE_H #define HAVE_SETPROCTITLE #define HAVE_STRLCAT #define HAVE_STRLCPY #define HAVE_TREE_H EOF cat <>$CONFIG_MK SRCS+= compat/strtonum.c CPPFLAGS+= -I/usr/pkg/include LDFLAGS+= -L/usr/pkg/lib EOF ;; # ------------------------------------------------------------------------------ *) echo Unable to configure for $FDM_PLATFORM exit 1 esac echo Configured for $FDM_PLATFORM exit 0 fdm-1.7+cvs20140912/strb.c0000600000175000017500000001005310651743055013322 0ustar hawkhawk/* $Id: strb.c,v 1.15 2007/07/25 21:52:45 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "fdm.h" void *strb_address(struct strb *, const char *); void strb_create(struct strb **sbp) { *sbp = xcalloc(1, STRBOFFSET); strb_clear(sbp); } void strb_clear(struct strb **sbp) { struct strb *sb = *sbp; sb->ent_used = 0; sb->ent_max = STRBENTRIES; sb->str_size = STRBBLOCK; sb->str_used = 0; sb = *sbp = xrealloc(sb, 1, STRB_SIZE(sb)); memset(STRB_BASE(sb), 0, sb->str_size + STRB_ENTSIZE(sb)); } void strb_destroy(struct strb **sbp) { xfree(*sbp); *sbp = NULL; } void strb_dump(struct strb *sb, const char *prefix, void (*p)(const char *, ...)) { struct strbent *sbe; u_int i; for (i = 0; i < sb->ent_used; i++) { sbe = STRB_ENTRY(sb, i); p("%s: %s: %s", prefix, STRB_KEY(sb, sbe), STRB_VALUE(sb, sbe)); } } void printflike3 strb_add(struct strb **sbp, const char *key, const char *value, ...) { va_list ap; va_start(ap, value); strb_vadd(sbp, key, value, ap); va_end(ap); } void strb_vadd(struct strb **sbp, const char *key, const char *value, va_list ap) { struct strb *sb = *sbp; size_t size, keylen, valuelen; u_int n; struct strbent sbe, *sbep; va_list aq; keylen = strlen(key) + 1; va_copy(aq, ap); valuelen = xvsnprintf(NULL, 0, value, aq) + 1; va_end(aq); size = sb->str_size; while (sb->str_size - sb->str_used < keylen + valuelen) { if (STRB_SIZE(sb) > SIZE_MAX / 2) fatalx("size too large"); sb->str_size *= 2; } if (size != sb->str_size) { sb = *sbp = xrealloc(sb, 1, STRB_SIZE(sb)); memmove( STRB_ENTBASE(sb), STRB_BASE(sb) + size, STRB_ENTSIZE(sb)); memset(STRB_BASE(sb) + size, 0, sb->str_size - size); } sbep = strb_address(sb, key); if (sbep == NULL) { if (sb->ent_used > sb->ent_max) { /* Allocate some more entries. */ n = sb->ent_max; size = STRB_SIZE(sb); if (sb->ent_max > UINT_MAX / 2) fatalx("ent_max too large"); sb->ent_max *= 2; if (STRB_SIZE(sb) < size) fatalx("size too large"); sb = *sbp = xrealloc(sb, 1, STRB_SIZE(sb)); memset(STRB_ENTRY(sb, n), 0, STRB_ENTSIZE(sb) / 2); } sbep = STRB_ENTRY(sb, sb->ent_used); sb->ent_used++; sbe.key = sb->str_used; memcpy(STRB_KEY(sb, &sbe), key, keylen); sb->str_used += keylen; } else memcpy(&sbe, sbep, sizeof sbe); sbe.value = sb->str_used; xvsnprintf(STRB_VALUE(sb, &sbe), valuelen, value, ap); sb->str_used += valuelen; memcpy(sbep, &sbe, sizeof sbe); } void * strb_address(struct strb *sb, const char *key) { struct strbent sbe; u_int i; for (i = 0; i < sb->ent_used; i++) { memcpy(&sbe, STRB_ENTRY(sb, i), sizeof sbe); if (strcmp(key, STRB_KEY(sb, &sbe)) == 0) return (STRB_ENTRY(sb, i)); } return (NULL); } struct strbent * strb_find(struct strb *sb, const char *key) { static struct strbent sbe; void *sbep; sbep = strb_address(sb, key); if (sbep == NULL) return (NULL); memcpy(&sbe, sbep, sizeof sbe); return (&sbe); } struct strbent * strb_match(struct strb *sb, const char *patt) { static struct strbent sbe; u_int i; for (i = 0; i < sb->ent_used; i++) { memcpy(&sbe, STRB_ENTRY(sb, i), sizeof sbe); if (fnmatch(patt, STRB_KEY(sb, &sbe), 0) == 0) return (&sbe); } return (NULL); } fdm-1.7+cvs20140912/deliver-mbox.c0000600000175000017500000001446511030761274014753 0ustar hawkhawk/* $Id: deliver-mbox.c,v 1.71 2008/06/26 18:41:00 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" /* With gcc 2.95.x, you can't include zlib.h before openssl.h. */ #include int deliver_mbox_deliver(struct deliver_ctx *, struct actitem *); void deliver_mbox_desc(struct actitem *, char *, size_t); int deliver_mbox_write(FILE *, gzFile, const void *, size_t); struct deliver deliver_mbox = { "mbox", DELIVER_ASUSER, deliver_mbox_deliver, deliver_mbox_desc }; int deliver_mbox_write(FILE *f, gzFile gzf, const void *buf, size_t len) { if (gzf == NULL) { if (fwrite(buf, len, 1, f) != 1) { errno = EIO; return (-1); } } else { if ((size_t) gzwrite(gzf, buf, len) != len) { errno = EIO; return (-1); } } return (0); } int deliver_mbox_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_mbox_data *data = ti->data; char *path, *ptr, *lptr, *from = NULL; const char *msg; size_t len, llen; int fd, saved_errno; FILE *f; gzFile gzf; long long used; sigset_t set, oset; struct stat sb; f = gzf = NULL; fd = -1; path = replacepath(&data->path, m->tags, m, &m->rml, dctx->udata->home); if (path == NULL || *path == '\0') { log_warnx("%s: empty path", a->name); goto error; } if (data->compress) { len = strlen(path); if (len < 3 || strcmp(path + len - 3, ".gz") != 0) { path = xrealloc(path, 1, len + 4); strlcat(path, ".gz", len + 4); } } log_debug2("%s: saving to mbox %s", a->name, path); /* Save the mbox path. */ add_tag(&m->tags, "mbox_file", "%s", path); /* Check permissions and ownership. */ if (stat(path, &sb) != 0) { if (conf.no_create || errno != ENOENT) goto error_log; log_debug2("%s: creating %s", a->name, xdirname(path)); if (xmkpath(xdirname(path), -1, conf.file_group, DIRMODE) != 0) goto error_log; } else { if ((msg = checkmode(&sb, UMASK(FILEMODE))) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); if ((msg = checkowner(&sb, -1)) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); if ((msg = checkgroup(&sb, conf.file_group)) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); } /* Create or open the mbox. */ used = 0; do { if (conf.no_create) fd = openlock(path, O_WRONLY|O_APPEND, conf.lock_types); else { fd = createlock(path, O_WRONLY|O_APPEND, -1, conf.file_group, FILEMODE, conf.lock_types); } if (fd == -1 && errno == EEXIST) fd = openlock(path, O_WRONLY|O_APPEND, conf.lock_types); if (fd == -1) { if (errno == EAGAIN) { if (locksleep(a->name, path, &used) != 0) goto error; continue; } goto error_log; } } while (fd < 0); /* Open gzFile or FILE * for writing. */ if (data->compress) { if ((gzf = gzdopen(fd, "a")) == NULL) { errno = ENOMEM; goto error_log; } } else { if ((f = fdopen(fd, "a")) == NULL) goto error_log; } /* * mboxes are a pain: if we are interrupted after this we risk * having written a partial mail. So, block SIGTERM until we're * done. */ sigemptyset(&set); sigaddset(&set, SIGTERM); if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) fatal("sigprocmask failed"); /* Write the from line. */ from = make_from(m, dctx->udata->name); if (deliver_mbox_write(f, gzf, from, strlen(from)) < 0) { xfree(from); goto error_unblock; } if (deliver_mbox_write(f, gzf, "\n", 1) < 0) { xfree(from); goto error_unblock; } log_debug3("%s: using from line: %s", a->name, from); xfree(from); /* Write the mail, escaping from lines. */ line_init(m, &ptr, &len); while (ptr != NULL) { if (ptr != m->data) { /* Skip leading >s. */ lptr = ptr; llen = len; while (*lptr == '>' && llen > 0) { lptr++; llen--; } if (llen >= 5 && strncmp(lptr, "From ", 5) == 0) { log_debug2("%s: quoting from line: %.*s", a->name, (int) len - 1, ptr); if (deliver_mbox_write(f, gzf, ">", 1) < 0) goto error_unblock; } } if (deliver_mbox_write(f, gzf, ptr, len) < 0) goto error_unblock; line_next(m, &ptr, &len); } /* Append newlines. */ if (m->data[m->size - 1] == '\n') { if (deliver_mbox_write(f, gzf, "\n", 1) < 0) goto error_unblock; } else { if (deliver_mbox_write(f, gzf, "\n\n", 2) < 0) goto error_unblock; } /* Flush buffers and sync. */ if (gzf == NULL) { if (fflush(f) != 0) goto error_unblock; } else { if (gzflush(gzf, Z_FINISH) != Z_OK) { errno = EIO; goto error_unblock; } } if (fsync(fd) != 0) goto error_unblock; if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) fatal("sigprocmask failed"); if (gzf != NULL) gzclose(gzf); if (f != NULL) fclose(f); closelock(fd, path, conf.lock_types); xfree(path); return (DELIVER_SUCCESS); error_unblock: saved_errno = errno; if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) fatal("sigprocmask failed"); errno = saved_errno; error_log: log_warn("%s: %s", a->name, path); error: if (gzf != NULL) gzclose(gzf); if (f != NULL) fclose(f); if (fd != -1) closelock(fd, path, conf.lock_types); if (path != NULL) xfree(path); return (DELIVER_FAILURE); } void deliver_mbox_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_mbox_data *data = ti->data; if (data->compress) xsnprintf(buf, len, "mbox \"%s\" compress", data->path.str); else xsnprintf(buf, len, "mbox \"%s\"", data->path.str); } fdm-1.7+cvs20140912/deliver-drop.c0000600000175000017500000000267610602544475014761 0ustar hawkhawk/* $Id: deliver-drop.c,v 1.18 2007/03/28 19:59:57 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_drop_deliver(struct deliver_ctx *, struct actitem *); void deliver_drop_desc(struct actitem *, char *, size_t); struct deliver deliver_drop = { "drop", DELIVER_INCHILD, deliver_drop_deliver, deliver_drop_desc }; int deliver_drop_deliver(struct deliver_ctx *dctx, unused struct actitem *ti) { struct mail *m = dctx->mail; m->decision = DECISION_DROP; return (DELIVER_SUCCESS); } void deliver_drop_desc(unused struct actitem *ti, char *buf, size_t len) { strlcpy(buf, "drop", len); } fdm-1.7+cvs20140912/match-account.c0000600000175000017500000000376210665542520015106 0ustar hawkhawk/* $Id: match-account.c,v 1.2 2007/08/30 13:25:36 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #include "match.h" int match_account_match(struct mail_ctx *, struct expritem *); void match_account_desc(struct expritem *, char *, size_t); struct match match_account = { "account", match_account_match, match_account_desc }; int match_account_match(struct mail_ctx *mctx, struct expritem *ei) { struct match_account_data *data = ei->data; struct account *a = mctx->account; struct mail *m = mctx->mail; char *s; u_int i; for (i = 0; i < ARRAY_LENGTH(data->accounts); i++) { s = replacestr( &ARRAY_ITEM(data->accounts, i), m->tags, m, &m->rml); if (s == NULL || *s == '\0') { if (s != NULL) xfree(s); log_warnx("%s: empty account", a->name); return (MATCH_ERROR); } if (account_match(s, a->name)) { xfree(s); return (MATCH_TRUE); } xfree(s); } return (MATCH_FALSE); } void match_account_desc(struct expritem *ei, char *buf, size_t len) { struct match_account_data *data = ei->data; char *accounts; accounts = fmt_replstrs("account ", data->accounts); strlcpy(buf, accounts, len); xfree(accounts); } fdm-1.7+cvs20140912/fdm.10000600000175000017500000001025611123737065013040 0ustar hawkhawk.\" $Id: fdm.1,v 1.31 2008/12/22 16:20:05 nicm Exp $ .\" .\" Copyright (c) 2006 Nicholas Marriott .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd December 22, 2008 .Dt FDM 1 .Os .Sh NAME .Nm fdm .Nd "fetch and deliver mail" .Sh SYNOPSIS .Nm fdm .Bk -words .Op Fl hklmnqv .Op Fl a Ar account .Oo Fl D Ar name Ns = .Ar value Oc .Op Fl f Ar conffile .Op Fl u Ar user .Op Fl x Ar account .Op Ar fetch | poll .Ek .Sh DESCRIPTION The .Nm program fetches mail from a POP3 or IMAP server or from .Em stdin and delivers it based on a ruleset in the configuration file. .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Ar name Process only the specified account. This option may appear multiple times. The account name may include shell glob characters to match multiple accounts. .Pp .It Fl D Ar name Ns = Ns Ar value This option defines a macro for use when parsing the configuration file. The macro name must be prefixed with .Li $ or .Li % to specify a string or numeric macro. This option may appear multiple times. .It Fl f Ar conffile Specify the configuration file location. Default is .Pa ~/.fdm.conf , or .Pa /etc/fdm.conf if that doesn't exist. .Pp .It Fl h Look at the .Ev HOME environment variable to ascertain the user's home directory. .It Fl k Keep all mail after delivery, regardless of whether it matches a .Ic drop action. Note that mails kept in this way will be refetched by .Nm if it is run again on the same account. .It Fl l Log using .Xr syslog 3 rather than to .Em stderr . .Pp .It Fl m Ignore the lock file and run regardless of other instances of .Nm . .It Fl n Do not process any accounts, just verify the configuration file syntax and exit. .Pp .It Fl q Quiet mode. Only print errors. .Pp .It Fl u Ar user Specify the default user for delivery. This overrides the .Ic default-user option in the configuration file. .Pp .It Fl v Request verbose logging. This option may be specified multiple times. .Fl vv will print information on configuration (useful with .Fl n ) . .Fl vvvv duplicates all traffic to and from remote servers to .Em stdout . This feature is disabled when using the .Fl l flag. .Pp .It Fl x Ar name Exclude the named account. Multiple .Fl x options may be specified. As with .Fl a , shell glob characters may be used. .Pp .It Ar fetch | poll | cache The .Ar fetch command instructs .Nm to fetch and deliver messages. The .Ar poll command polls the accounts in the configuration file and reports a message count for each. .Ar cache allows .Nm cache files to be manipulated: see the next section. .Pp .El .Sh CACHE COMMANDS The following cache manipulation commands are supported: .Bl -tag -width Ds .It Ic cache Ic add Ar path Ar string .It Ic cache Ic remove Ar path Ar string Add or remove .Ar string as a key in the cache at .Ar path . .It Ic cache Ic list Op Ar path List the number of keys in the specified cache, or if .Ar path is omitted, in all caches declared in the configuration file. .It Ic cache Ic dump Ar path Dump the contents of the cache .Ar path to stdout. Each key is printed followed by a space and the timestamp as Unix time. .It Ic cache Ic clear Ar path Delete all keys from the cache at .Ar path . .El .Sh FILES .Bl -tag -width Ds -compact .It Pa ~/.fdm.conf default .Nm configuration file .It Pa /etc/fdm.conf default system-wide configuration file .It Pa ~/.fdm.lock default lock file .It Pa /var/db/fdm.lock lock file for root user .El .Sh SEE ALSO .Xr mail 1 , .Xr fdm.conf 5 , .Xr sendmail 8 .Sh AUTHORS .An Nicholas Marriott Aq nicm@users.sourceforge.net fdm-1.7+cvs20140912/pcre.c0000600000175000017500000000461510666021241013300 0ustar hawkhawk/* $Id: pcre.c,v 1.13 2007/08/31 14:16:01 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef PCRE #include #include #include #include "fdm.h" int re_compile(struct re *re, const char *s, int flags, char **cause) { const char *error; int off; if (s == NULL) fatalx("null regexp"); re->str = xstrdup(s); if (*s == '\0') return (0); re->flags = flags; flags = PCRE_MULTILINE; if (re->flags & RE_IGNCASE) flags |= PCRE_CASELESS; if ((re->pcre = pcre_compile(s, flags, &error, &off, NULL)) == NULL) { *cause = xstrdup(error); return (-1); } return (0); } int re_string(struct re *re, const char *s, struct rmlist *rml, char **cause) { return (re_block(re, s, strlen(s), rml, cause)); } int re_block(struct re *re, const void *buf, size_t len, struct rmlist *rml, char **cause) { int res, pm[NPMATCH * 3]; u_int i, j; if (len > INT_MAX) fatalx("buffer too big"); if (rml != NULL) memset(rml, 0, sizeof *rml); /* If the regexp is empty, just check whether the buffer is empty. */ if (*re->str == '\0') { if (len == 0) return (1); return (0); } res = pcre_exec(re->pcre, NULL, buf, len, 0, 0, pm, NPMATCH * 3); if (res < 0 && res != PCRE_ERROR_NOMATCH) { xasprintf(cause, "%s: regexec failed", re->str); return (-1); } if (rml != NULL) { for (i = 0; i < NPMATCH; i++) { j = i * 2; if (pm[j + 1] <= pm[j]) break; rml->list[i].valid = 1; rml->list[i].so = pm[j]; rml->list[i].eo = pm[j + 1]; } rml->valid = 1; } return (res != PCRE_ERROR_NOMATCH); } void re_free(struct re *re) { xfree(re->str); pcre_free(re->pcre); } #endif /* PCRE */ fdm-1.7+cvs20140912/re.c0000600000175000017500000000501210651743055012755 0ustar hawkhawk/* $Id: re.c,v 1.21 2007/07/25 21:52:45 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef PCRE #include #include #include "fdm.h" int re_compile(struct re *re, const char *s, int flags, char **cause) { int error; size_t len; char *buf; if (s == NULL) fatalx("null regexp"); re->str = xstrdup(s); if (*s == '\0') return (0); re->flags = flags; flags = REG_EXTENDED|REG_NEWLINE; if (re->flags & RE_NOSUBST) flags |= REG_NOSUB; if (re->flags & RE_IGNCASE) flags |= REG_ICASE; if ((error = regcomp(&re->re, s, flags)) != 0) { len = regerror(error, &re->re, NULL, 0); buf = xmalloc(len); regerror(error, &re->re, buf, len); xasprintf(cause, "%s%s", s, buf); return (-1); } return (0); } int re_string(struct re *re, const char *s, struct rmlist *rml, char **cause) { return (re_block(re, s, strlen(s), rml, cause)); } int re_block(struct re *re, const void *buf, size_t len, struct rmlist *rml, char **cause) { int res; regmatch_t pm[NPMATCH]; u_int i; if (rml != NULL) memset(rml, 0, sizeof *rml); /* If the regexp is empty, just check whether the buffer is empty. */ if (*re->str == '\0') { if (len == 0) return (1); return (0); } memset(pm, 0, sizeof pm); pm[0].rm_so = 0; pm[0].rm_eo = len; res = regexec(&re->re, buf, NPMATCH, pm, REG_STARTEND); if (res != 0 && res != REG_NOMATCH) { xasprintf(cause, "%s: regexec failed", re->str); return (-1); } if (rml != NULL) { for (i = 0; i < NPMATCH; i++) { if (pm[i].rm_eo <= pm[i].rm_so) break; rml->list[i].valid = 1; rml->list[i].so = pm[i].rm_so; rml->list[i].eo = pm[i].rm_eo; } rml->valid = 1; } return (res == 0); } void re_free(struct re *re) { xfree(re->str); regfree(&re->re); } #endif /* !PCRE */ fdm-1.7+cvs20140912/GNUmakefile0000600000175000017500000000275212227440104014253 0ustar hawkhawk# $Id: GNUmakefile,v 1.113 2013/10/16 07:29:08 nicm Exp $ .PHONY: clean VERSION= 1.7 FDEBUG= 1 CC?= gcc YACC= yacc -d CPPFLAGS+= -I/usr/local/include -I. CFLAGS+= -DBUILD="\"$(VERSION)\"" LDFLAGS+= -L/usr/local/lib LIBS+= -lssl -lcrypto -ltdb -lz ifdef FDEBUG LDFLAGS+= -rdynamic CFLAGS+= -g -ggdb -DDEBUG LIBS+= -ldl CFLAGS+= -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 CFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations CFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare CFLAGS+= -Wundef -Wbad-function-cast -Winline -Wcast-align endif ifdef COURIER CFLAGS+= -DLOOKUP_COURIER LIBS+= -lcourierauth endif ifdef PCRE CFLAGS+= -DPCRE LIBS+= -lpcre endif PREFIX?= /usr/local BINDIR?= $(PREFIX)/bin MANDIR?= $(PREFIX)/man INSTALLDIR= install -d INSTALLBIN= install -m 755 INSTALLMAN= install -m 644 SRCS= $(shell echo *.c|sed 's|y.tab.c||g'; echo y.tab.c) include config.mk OBJS= $(patsubst %.c,%.o,$(SRCS)) all: fdm lex.o: y.tab.c y.tab.c: parse.y $(YACC) $< fdm: $(OBJS) $(CC) $(LDFLAGS) -o fdm $+ $(LIBS) depend: $(SRCS) $(CC) $(CPPFLAGS) -MM $(SRCS) > .depend clean: rm -f fdm *.o .depend *~ *.core *.log compat/*.o y.tab.[ch] clean-all: clean rm -f config.h config.mk install: all $(INSTALLDIR) $(DESTDIR)$(BINDIR) $(INSTALLBIN) fdm $(DESTDIR)$(BINDIR) $(INSTALLDIR) $(DESTDIR)$(MANDIR)/man1 $(INSTALLMAN) fdm.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALLDIR) $(DESTDIR)$(MANDIR)/man5 $(INSTALLMAN) fdm.conf.5 $(DESTDIR)$(MANDIR)/man5 fdm-1.7+cvs20140912/buffer.c0000600000175000017500000001050511047077051013617 0ustar hawkhawk/* $Id: buffer.c,v 1.11 2008/08/08 17:11:05 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" /* Create a buffer. */ struct buffer * buffer_create(size_t size) { struct buffer *b; if (size == 0) fatalx("zero size"); b = xcalloc(1, sizeof *b); b->base = xmalloc(size); b->space = size; return (b); } /* Destroy a buffer. */ void buffer_destroy(struct buffer *b) { xfree(b->base); xfree(b); } /* Empty a buffer. */ void buffer_clear(struct buffer *b) { b->size = 0; b->off = 0; } /* Ensure free space for size in buffer. */ void buffer_ensure(struct buffer *b, size_t size) { if (size == 0) fatalx("zero size"); if (BUFFER_FREE(b) >= size) return; if (b->off > 0) { if (b->size > 0) memmove(b->base, b->base + b->off, b->size); b->off = 0; } if (SIZE_MAX - b->size < size) fatalx("size too big"); while (b->space < b->size + size) { b->base = xrealloc(b->base, 2, b->space); b->space *= 2; } } /* Adjust buffer after data appended. */ void buffer_add(struct buffer *b, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->space - b->size) fatalx("overflow"); b->size += size; } /* Reverse buffer add. */ void buffer_reverse_add(struct buffer *b, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->size) fatalx("underflow"); b->size -= size; } /* Adjust buffer after data removed. */ void buffer_remove(struct buffer *b, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->size) fatalx("underflow"); b->size -= size; b->off += size; } /* Reverse buffer remove. */ void buffer_reverse_remove(struct buffer *b, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->off) fatalx("overflow"); b->size += size; b->off -= size; } /* Insert a section into the buffer. */ void buffer_insert_range(struct buffer *b, size_t base, size_t size) { if (size == 0) fatalx("zero size"); if (base > b->size) fatalx("range outside buffer"); buffer_ensure(b, size); memmove(b->base + b->off + base + size, b->base + b->off + base, b->size - base); b->size += size; } /* Delete a section from the buffer. */ void buffer_delete_range(struct buffer *b, size_t base, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->size) fatalx("size too big"); if (base + size > b->size) fatalx("range outside buffer"); memmove(b->base + b->off + base, b->base + b->off + base + size, b->size - base - size); b->size -= size; } /* Copy data into a buffer. */ void buffer_write(struct buffer *b, const void *data, size_t size) { if (size == 0) fatalx("zero size"); buffer_ensure(b, size); memcpy(BUFFER_IN(b), data, size); buffer_add(b, size); } /* Copy data out of a buffer. */ void buffer_read(struct buffer *b, void *data, size_t size) { if (size == 0) fatalx("zero size"); if (size > b->size) fatalx("underflow"); memcpy(data, BUFFER_OUT(b), size); buffer_remove(b, size); } /* Store an 8-bit value. */ void buffer_write8(struct buffer *b, uint8_t n) { buffer_ensure(b, 1); BUFFER_IN(b)[0] = n; buffer_add(b, 1); } /* Store a 16-bit value. */ void buffer_write16(struct buffer *b, uint16_t n) { buffer_ensure(b, 2); BUFFER_IN(b)[0] = n & 0xff; BUFFER_IN(b)[1] = n >> 8; buffer_add(b, 2); } /* Extract an 8-bit value. */ uint8_t buffer_read8(struct buffer *b) { uint8_t n; n = BUFFER_OUT(b)[0]; buffer_remove(b, 1); return (n); } /* Extract a 16-bit value. */ uint16_t buffer_read16(struct buffer *b) { uint16_t n; n = BUFFER_OUT(b)[0] | (BUFFER_OUT(b)[1] << 8); buffer_remove(b, 2); return (n); } fdm-1.7+cvs20140912/file.c0000600000175000017500000001562311234322645013273 0ustar hawkhawk/* $Id: file.c,v 1.11 2009/07/30 13:52:37 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "fdm.h" int mklock(u_int, const char *); void rmlock(u_int, const char *); int lockfd(u_int, int); /* Print path into buffer. */ int ppath(char *buf, size_t len, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = vppath(buf, len, fmt, ap); va_end(ap); return (n); } /* Make path into buffer. */ int vppath(char *buf, size_t len, const char *fmt, va_list ap) { if ((size_t) xvsnprintf(buf, len, fmt, ap) >= len) { errno = ENAMETOOLONG; return (-1); } return (0); } /* Make lock file. */ int mklock(u_int locks, const char *path) { char lock[MAXPATHLEN]; int fd; if (!(locks & LOCK_DOTLOCK)) return (0); if (ppath(lock, sizeof lock, "%s.lock", path) != 0) return (-1); fd = xcreate(lock, O_WRONLY, -1, -1, S_IRUSR|S_IWUSR); if (fd == -1) { if (errno == EEXIST) errno = EAGAIN; return (-1); } close(fd); cleanup_register(lock); return (0); } /* Remove lock file. */ void rmlock(u_int locks, const char *path) { char lock[MAXPATHLEN]; if (!(locks & LOCK_DOTLOCK)) return; if (ppath(lock, sizeof lock, "%s.lock", path) != 0) fatal("unlink failed"); if (unlink(lock) != 0) fatal("unlink failed"); cleanup_deregister(lock); } /* Lock file descriptor. */ int lockfd(u_int locks, int fd) { struct flock fl; if (locks & LOCK_FLOCK) { if (flock(fd, LOCK_EX|LOCK_NB) != 0) { if (errno == EWOULDBLOCK) errno = EAGAIN; return (-1); } } if (locks & LOCK_FCNTL) { memset(&fl, 0, sizeof fl); fl.l_start = 0; fl.l_len = 0; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; if (fcntl(fd, F_SETLK, &fl) == -1) { /* fcntl already returns EAGAIN if needed. */ return (-1); } } return (0); } /* * Open a file, locking using the lock types specified. Returns EAGAIN if lock * failed. */ int openlock(const char *path, int flags, u_int locks) { int fd, saved_errno; if (mklock(locks, path) != 0) return (-1); if ((fd = open(path, flags, 0)) == -1) goto error; if (lockfd(locks, fd) != 0) goto error; return (fd); error: saved_errno = errno; close(fd); rmlock(locks, path); errno = saved_errno; return (-1); } /* Sleep for lock. */ int locksleep(const char *hdr, const char *path, long long *used) { useconds_t us; if (*used == 0) srandom((u_int) getpid()); us = LOCKSLEEPTIME + (random() % LOCKSLEEPTIME); log_debug3("%s: %s: " "sleeping %.3f seconds for lock", hdr, path, us / 1000000.0); usleep(us); *used += us; if (*used < LOCKTOTALTIME) return (0); log_warnx("%s: %s: " "couldn't get lock in %.3f seconds", hdr, path, *used / 1000000.0); return (-1); } /* Create a locked file. */ int createlock( const char *path, int flags, uid_t uid, gid_t gid, mode_t mode, u_int locks) { int fd, saved_errno; if (mklock(locks, path) != 0) return (-1); if ((fd = xcreate(path, flags, uid, gid, mode)) == -1) goto error; if (lockfd(locks, fd) != 0) goto error; return (fd); error: saved_errno = errno; close(fd); rmlock(locks, path); errno = saved_errno; return (-1); } /* Close locked file and remove locks. */ void closelock(int fd, const char *path, u_int locks) { close(fd); rmlock(locks, path); } /* Create a file. */ int xcreate(const char *path, int flags, uid_t uid, gid_t gid, mode_t mode) { int fd; if ((fd = open(path, flags|O_CREAT|O_EXCL, mode)) == -1) return (-1); if (uid != (uid_t) -1 || gid != (gid_t) -1) { if (fchown(fd, uid, gid) != 0) return (-1); } return (fd); } /* Make a directory. */ int xmkdir(const char *path, uid_t uid, gid_t gid, mode_t mode) { if (mkdir(path, mode) != 0) return (-1); if (uid != (uid_t) -1 || gid != (gid_t) -1) { if (chown(path, uid, gid) != 0) return (-1); } return (0); } /* Create entire path. */ int xmkpath(const char *path, uid_t uid, gid_t gid, mode_t mode) { struct stat sb; char *copy, *ptr, ch; copy = ptr = xstrdup(path); do { ptr += strspn(ptr, "/"); ptr += strcspn(ptr, "/"); ch = *ptr; *ptr = '\0'; if (stat(copy, &sb) != 0) { if (errno == ENOENT && xmkdir(copy, uid, gid, mode) != 0 && errno != EEXIST) return (-1); } else if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; return (-1); } *ptr = ch; } while (ch != '\0'); xfree(copy); return (0); } /* Check mode of file. */ const char * checkmode(struct stat *sb, mode_t mode) { static char msg[128]; if ((sb->st_mode & ACCESSPERMS) == mode) return (NULL); xsnprintf(msg, sizeof msg, "bad permissions:" " %o%o%o, should be %o%o%o", MODE(sb->st_mode), MODE(mode)); return (msg); } /* Check owner of file. */ const char * checkowner(struct stat *sb, uid_t uid) { static char msg[128]; if (uid == (uid_t) -1) uid = geteuid(); if (sb->st_uid == uid) return (NULL); xsnprintf(msg, sizeof msg, "bad owner: %lu, should be %lu", (u_long) sb->st_uid, (u_long) uid); return (msg); } /* Check group of file. */ const char * checkgroup(struct stat *sb, gid_t gid) { static char msg[128]; if (gid == (gid_t) -1) gid = getgid(); if (sb->st_gid == gid) return (NULL); xsnprintf(msg, sizeof msg, "bad group: %lu, should be %lu", (u_long) sb->st_gid, (u_long) gid); return (msg); } /* * Move file oldpath to newpath. oldpath is always removed even in case of * failure. * * It returns 0 on success and -1 on failure with errno set. * * This function use link + unlink or stat + rename if link fail with EXDEV. * (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=538194) */ int safemove(const char *oldpath, const char *newpath) { int ret; int errsave; struct stat sb; ret = link(oldpath, newpath); if (ret != 0 && errno == EXDEV) { ret = stat(newpath, &sb); if (ret == -1) { if (errno == ENOENT) ret = rename(oldpath, newpath); } else { ret = -1; errno = EEXIST; } } errsave = errno; if (unlink(oldpath) != 0 && errno != ENOENT) fatal("unlink failed"); errno = errsave; return (ret); } fdm-1.7+cvs20140912/fdm-sanitize0000700000175000017500000000321210654334643014523 0ustar hawkhawk#!/usr/bin/awk -f # # $Id: fdm-sanitize,v 1.8 2007/08/02 11:01:55 nicm Exp $ # # Copyright (c) 2006 Nicholas Marriott # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # If the output of fdm -vvvv is piped to this script, it will remove usernames # and passwords from IMAP/POP3 output, for example: # # fdm -vvvv f 2>&1|fdm-sanitize|tee saved-output # BEGIN { imap_user = 0; imap_pass = 0; } /> [0-9]+ LOGIN \{[0-9]+\}/ { imap_user = 1; print ($1 " " $2 " " $3 " {*}"); fflush(); next; } /> .+ \{[0-9]+\}/ { if (imap_user) { print ("> * {*}"); fflush(); imap_user = 0; imap_pass = 1; next; } print ($0); fflush(); next; } /> APOP .+/ { print ("> APOP * *"); fflush(); next; } /> USER .+/ { print ("> USER *"); fflush(); next; } /> PASS .+/ { print ("> PASS *"); fflush(); next; } /> .+/ { if (imap_pass) { print ("> *"); fflush(); imap_pass = 0; next; } print ($0); fflush(); next; } /.*/ { print ($0); fflush(); } fdm-1.7+cvs20140912/deliver-maildir.c0000600000175000017500000001372511234322645015426 0ustar hawkhawk/* $Id: deliver-maildir.c,v 1.60 2009/07/30 13:52:37 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" int deliver_maildir_deliver(struct deliver_ctx *, struct actitem *); void deliver_maildir_desc(struct actitem *, char *, size_t); char *deliver_maildir_host(void); int deliver_maildir_create(struct account *, const char *); struct deliver deliver_maildir = { "maildir", DELIVER_ASUSER, deliver_maildir_deliver, deliver_maildir_desc }; /* * Return hostname with '/' replaced with "\057" and ':' with "\072". This is a * bit inefficient but sod it. Why they couldn't both be replaced by _ is * beyond me... * * The hostname will be truncated if these additions make it longer than * MAXHOSTNAMELEN. No clue if this is right. */ char * deliver_maildir_host(void) { static char host1[MAXHOSTNAMELEN], host2[MAXHOSTNAMELEN]; char ch; size_t first, last; if (gethostname(host1, sizeof host1) != 0) fatal("gethostname failed"); *host2 = '\0'; last = strcspn(host1, "/:"); if (host1[last] == '\0') return (host1); first = 0; do { ch = host1[first + last]; host1[first + last] = '\0'; strlcat(host2, host1 + first, sizeof host2); switch (ch) { case '/': strlcat(host2, "\\057", sizeof host2); break; case ':': strlcat(host2, "\\072", sizeof host2); break; } first += last + 1; last = strcspn(host1 + first, "/:"); } while (ch != '\0'); return (host2); } /* Create a new maildir. */ int deliver_maildir_create(struct account *a, const char *maildir) { struct stat sb; const char *msg, *names[] = { "", "/cur", "/new", "/tmp", NULL }; char path[MAXPATHLEN]; u_int i; if (conf.no_create) return (0); log_debug2("%s: creating %s", a->name, xdirname(maildir)); if (xmkpath(xdirname(maildir), -1, conf.file_group, DIRMODE) != 0) { log_warn("%s: %s", a->name, maildir); return (-1); } for (i = 0; names[i] != NULL; i++) { if (ppath(path, sizeof path, "%s%s", maildir, names[i]) != 0) goto error; if (xmkdir(path, -1, conf.file_group, DIRMODE) == 0) { log_debug2("%s: creating %s", a->name, path); continue; } if (errno != EEXIST) goto error; if (stat(path, &sb) != 0) goto error; if ((msg = checkmode(&sb, UMASK(DIRMODE))) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); if ((msg = checkowner(&sb, -1)) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); if ((msg = checkgroup(&sb, conf.file_group)) != NULL) log_warnx("%s: %s: %s", a->name, path, msg); } return (0); error: log_warn("%s: %s%s", a->name, path, names[i]); return (-1); } int deliver_maildir_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_maildir_data *data = ti->data; static u_int delivered = 0; char *host, *name, *path; char src[MAXPATHLEN], dst[MAXPATHLEN]; int fd; ssize_t n; name = NULL; fd = -1; path = replacepath(&data->path, m->tags, m, &m->rml, dctx->udata->home); if (path == NULL || *path == '\0') { log_warnx("%s: empty path", a->name); goto error; } log_debug2("%s: saving to maildir %s", a->name, path); /* Create the maildir. */ if (deliver_maildir_create(a, path) != 0) goto error; /* Find host name. */ host = deliver_maildir_host(); restart: /* Find a suitable name in tmp. */ do { if (name != NULL) xfree(name); xasprintf(&name, "%ld.%ld_%u.%s", (long) time(NULL), (long) getpid(), delivered, host); if (ppath(src, sizeof src, "%s/tmp/%s", path, name) != 0) { log_warn("%s: %s/tmp/%s", a->name, path, name); goto error; } log_debug3("%s: trying %s/tmp/%s", a->name, path, name); fd = xcreate(src, O_WRONLY, -1, conf.file_group, FILEMODE); if (fd == -1 && errno != EEXIST) goto error_log; delivered++; } while (fd == -1); cleanup_register(src); /* Write the message. */ log_debug2("%s: writing to %s", a->name, src); n = write(fd, m->data, m->size); if (n < 0 || (size_t) n != m->size || fsync(fd) != 0) goto error_cleanup; close(fd); fd = -1; /* * Create the new path and attempt to link it. A failed link jumps * back to find another name in the tmp directory. */ if (ppath(dst, sizeof dst, "%s/new/%s", path, name) != 0) goto error_cleanup; log_debug2( "%s: moving .../tmp/%s to .../new/%s", a->name, name, name); if (safemove(src, dst) != 0) { if (errno == EEXIST) { log_debug2("%s: %s: moving failed", a->name, src); cleanup_deregister(src); goto restart; } goto error_cleanup; } cleanup_deregister(src); /* Save the mail file as a tag. */ add_tag(&m->tags, "mail_file", "%s", dst); xfree(name); xfree(path); return (DELIVER_SUCCESS); error_cleanup: cleanup_deregister(src); error_log: log_warn("%s: %s", a->name, src); error: if (fd != -1) close(fd); if (name != NULL) xfree(name); if (path != NULL) xfree(path); return (DELIVER_FAILURE); } void deliver_maildir_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_maildir_data *data = ti->data; xsnprintf(buf, len, "maildir \"%s\"", data->path.str); } fdm-1.7+cvs20140912/attach.c0000600000175000017500000002054010666021241013606 0ustar hawkhawk/* $Id: attach.c,v 1.29 2007/08/31 14:16:01 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" char *attach_type(struct mail *, char *, const char *, char **); struct attach *attach_get(struct mail *, char **, size_t *, const char *, int *); struct attach * attach_visit(struct attach *atr, u_int *n) { struct attach *atn; if (atr == NULL) return (NULL); if (TAILQ_EMPTY(&atr->children)) { atn = TAILQ_NEXT(atr, entry); if (atn == NULL) { if (n != NULL) (*n)--; atr = atr->parent; if (atr != NULL) atr = TAILQ_NEXT(atr, entry); } else atr = atn; } else { if (n != NULL) (*n)++; atr = TAILQ_FIRST(&atr->children); } return (atr); } void printflike2 attach_log(struct attach *atr, const char *fmt, ...) { va_list ap; char *prefix; u_int n; va_start(ap, fmt); xvasprintf(&prefix, fmt, ap); va_end(ap); n = 0; while (atr != NULL) { if (TAILQ_EMPTY(&atr->children)) { if (atr->name == NULL) { log_debug3("%s:%*s%u, %s: offset %zu, size %zu," " body %zu", prefix, n + 1, " ", atr->idx, atr->type, atr->data, atr->size, atr->body); } else { log_debug3("%s:%*s%u, %s: offset %zu, size %zu," " body %zu: %s", prefix, n + 1, " ", atr->idx, atr->type, atr->data, atr->size, atr->body, atr->name); } } else { log_debug3("%s:%*s%u, %s", prefix, n + 1, " ", atr->idx, atr->type); } atr = attach_visit(atr, &n); } xfree(prefix); } void attach_free(struct attach *atr) { struct attach *at; while (!TAILQ_EMPTY(&atr->children)) { at = TAILQ_FIRST(&atr->children); TAILQ_REMOVE(&atr->children, at, entry); attach_free(at); } if (atr->type != NULL) xfree(atr->type); if (atr->name != NULL) xfree(atr->name); xfree(atr); } char * attach_type(struct mail *m, char *hdr, const char *name, char **value) { size_t len, llen; ssize_t namelen; char *ptr, *type = NULL; *value = NULL; len = m->size - (hdr - m->data); if (len < 13 || strncasecmp(hdr, "content-type:", 13) != 0) goto error; len -= 13; hdr += 13; /* Skip spaces. */ while (len > 0 && isspace((u_char) *hdr)) { len--; hdr++; } if (len == 0) goto error; /* Find end of line. */ ptr = memchr(hdr, '\n', len); if (ptr == NULL) llen = len; else llen = ptr - hdr; /* Find type. */ ptr = memchr(hdr, ';', llen); if (ptr == NULL) ptr = hdr + llen; type = xmalloc(ptr - hdr + 1); memcpy(type, hdr, ptr - hdr); type[ptr - hdr] = '\0'; len -= ptr - hdr; hdr = ptr; /* If this is now the end of the line, return the type. */ if (len == 0 || *ptr == '\n') return (type); /* Skip the semicolon. */ len--; hdr++; /* * Now follows a set of attributes of the form name=value, separated * by semicolons, possibly crossing multiple lines and possibly with * the value enclosed in quotes. */ namelen = strlen(name); for (;;) { /* Skip spaces and newlines. */ while (len > 0 && (isspace((u_char) *hdr) || *hdr == '\n')) { hdr++; len--; } if (len == 0) goto error; /* Find end of line. */ ptr = memchr(hdr, '\n', len); if (ptr == NULL) llen = len; else llen = ptr - hdr; /* Find the end of the attribute name. */ ptr = memchr(hdr, '=', llen); if (ptr == NULL) break; if (ptr - hdr == namelen && strncmp(hdr, name, namelen) == 0) { llen -= (ptr - hdr + 1); hdr = ptr + 1; ptr = memchr(hdr, ';', llen); if (ptr != NULL) llen = ptr - hdr; if (*hdr == '"') { if (llen < 2 || hdr[llen - 1] != '"') goto error; hdr++; llen -= 2; } *value = xmalloc(llen + 1); memcpy(*value, hdr, llen); (*value)[llen] = '\0'; break; } /* Skip to next semicolon. */ ptr = memchr(hdr, ';', llen); if (ptr == NULL) break; hdr = ptr + 1; len -= (ptr - hdr) + 1; } return (type); error: if (type != NULL) xfree(type); if (*value != NULL) { xfree(*value); *value = NULL; } return (NULL); } struct attach * attach_build(struct mail *m) { struct attach *atr = NULL, *at; char *hdr, *ptr, *b = NULL, *type; size_t len, bl; int last; u_int n; hdr = find_header(m, "content-type", &len, 0); if (hdr == NULL) return (NULL); type = attach_type(m, hdr, "boundary", &b); if (type == NULL || b == NULL) { if (type != NULL) xfree(type); goto error; } if (strncasecmp(type, "multipart/", 10) != 0) { xfree(type); goto error; } bl = strlen(b); atr = xmalloc(sizeof *atr); memset(atr, 0, sizeof *atr); TAILQ_INIT(&atr->children); atr->type = type; /* Find the first boundary. */ line_init(m, &ptr, &len); while (ptr != NULL) { if (ptr[0] == '-' && ptr[1] == '-') { if (len - 3 == bl && strncmp(ptr + 2, b, bl) == 0) break; } line_next(m, &ptr, &len); } if (ptr == NULL) goto error; /* Now iterate over the rest. */ last = 0; n = 0; while (ptr != NULL && !last) { if (ptr[0] == '-' && ptr[1] == '-') { if (len - 5 == bl && strncmp(ptr + 2, b, bl) == 0) break; } at = attach_get(m, &ptr, &len, b, &last); if (at == NULL) goto error; at->idx = n++; at->parent = atr; TAILQ_INSERT_TAIL(&atr->children, at, entry); } if (ptr == NULL) goto error; xfree(b); return (atr); error: if (atr != NULL) attach_free(atr); if (b != NULL) xfree(b); return (NULL); } struct attach * attach_get(struct mail *m, char **ptr, size_t *len, const char *b, int *last) { struct attach *atr, *at; char *name = NULL, *b2 = NULL; size_t bl, bl2; int last2; u_int n; bl = strlen(b); atr = xmalloc(sizeof *atr); memset(atr, 0, sizeof *atr); TAILQ_INIT(&atr->children); atr->data = *ptr - m->data; while (*ptr != NULL) { if (*len >= 13 && strncasecmp(*ptr, "content-type:", 13) == 0) break; line_next(m, ptr, len); } if (*ptr == NULL) goto error; atr->type = attach_type(m, *ptr, "name", &name); if (atr->type == NULL) { if (name != NULL) xfree(name); goto error; } atr->name = name; if (strncasecmp(atr->type, "multipart/", 10) != 0) { /* Skip the remaining headers. */ while (*ptr != NULL && *len > 1) line_next(m, ptr, len); if (*ptr == NULL) goto error; atr->body = *ptr - m->data; for (;;) { line_next(m, ptr, len); if (*ptr == NULL) break; if (*len < 3 || (*ptr)[0] != '-' || (*ptr)[1] != '-') continue; if (*len - 5 == bl && strncmp(*ptr + 2, b, bl) == 0 && strncmp(*ptr + bl + 2, "--", 2) == 0) { *last = 1; break; } if (*len - 3 == bl && strncmp(*ptr + 2, b, bl) == 0) break; } if (*ptr == NULL) goto error; atr->size = *ptr - m->data - atr->data; } else { /* XXX avoid doing this twice. */ xfree(atr->type); atr->type = attach_type(m, *ptr, "boundary", &b2); if (b2 == NULL) goto error; bl2 = strlen(b2); /* Find the first boundary. */ while (*ptr != NULL) { if ((*ptr)[0] == '-' && (*ptr)[1] == '-') { if (*len - 3 == bl2 && strncmp(*ptr + 2, b2, bl2) == 0) break; } line_next(m, ptr, len); } if (ptr == NULL) goto error; /* Now iterate over the rest. */ last2 = 0; n = 0; while (*ptr != NULL && !last2) { at = attach_get(m, ptr, len, b2, &last2); if (at == NULL) goto error; at->idx = n++; at->parent = atr; TAILQ_INSERT_TAIL(&atr->children, at, entry); } /* And skip on to the end of the multipart. */ while (*ptr != NULL) { if ((*ptr)[0] == '-' && (*ptr)[1] == '-') { if (*len - 5 == bl2 && strncmp(*ptr + 2, b2, bl2) == 0) break; } line_next(m, ptr, len); } if (*ptr == NULL) goto error; line_next(m, ptr, len); xfree(b2); } return (atr); error: if (b2 != NULL) xfree(b2); attach_free(atr); return (NULL); } fdm-1.7+cvs20140912/compat/0000700000175000017500000000000012404656670013472 5ustar hawkhawkfdm-1.7+cvs20140912/compat/strtonum.c0000600000175000017500000000346610530106667015536 0ustar hawkhawk/* $Id: strtonum.c,v 1.1 2006/11/19 17:00:39 nicm Exp $ */ /* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { long long ll = 0; char *ep; int error = 0; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE }, }; ev[0].err = errno; errno = 0; if (minval > maxval) error = INVALID; else { ll = strtoll(numstr, &ep, 10); if (numstr == ep || *ep != '\0') error = INVALID; else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) error = TOOSMALL; else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ll = 0; return (ll); } fdm-1.7+cvs20140912/compat/strlcat.c0000600000175000017500000000333710530106667015314 0ustar hawkhawk/* $Id: strlcat.c,v 1.1 2006/11/19 17:00:39 nicm Exp $ */ /* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } fdm-1.7+cvs20140912/compat/queue.h0000600000175000017500000004330110536620015014756 0ustar hawkhawk/* $Id: queue.h,v 1.2 2006/12/09 20:43:57 nicm Exp $ */ /* $OpenBSD: queue.h,v 1.31 2005/11/25 08:06:25 otto Exp $ */ /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues, and circular queues. * * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. */ #ifdef QUEUE_MACRO_DEBUG #define _Q_INVALIDATE(a) (a) = ((void *)-1) #else #define _Q_INVALIDATE(a) #endif /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List access methods. */ #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_END(head) NULL #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_FOREACH(var, head, field) \ for((var) = SLIST_FIRST(head); \ (var) != SLIST_END(head); \ (var) = SLIST_NEXT(var, field)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); \ ((var) = *(varp)) != SLIST_END(head); \ (varp) = &SLIST_NEXT((var), field)) /* * Singly-linked List functions. */ #define SLIST_INIT(head) { \ SLIST_FIRST(head) = SLIST_END(head); \ } #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) #define SLIST_REMOVE_NEXT(head, elm, field) do { \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->slh_first; \ \ while (curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ _Q_INVALIDATE((elm)->field.sle_next); \ } \ } while (0) /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List access methods */ #define LIST_FIRST(head) ((head)->lh_first) #define LIST_END(head) NULL #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_FOREACH(var, head, field) \ for((var) = LIST_FIRST(head); \ (var)!= LIST_END(head); \ (var) = LIST_NEXT(var, field)) /* * List functions. */ #define LIST_INIT(head) do { \ LIST_FIRST(head) = LIST_END(head); \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) #define LIST_REPLACE(elm, elm2, field) do { \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ (elm2)->field.le_next->field.le_prev = \ &(elm2)->field.le_next; \ (elm2)->field.le_prev = (elm)->field.le_prev; \ *(elm2)->field.le_prev = (elm2); \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { NULL, &(head).sqh_first } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue access methods. */ #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) #define SIMPLEQ_FOREACH(var, head, field) \ for((var) = SIMPLEQ_FIRST(head); \ (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (0) #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) /* * Tail queue definitions. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * tail queue access methods */ #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_END(head) NULL #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) /* XXX */ #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_EMPTY(head) \ (TAILQ_FIRST(head) == TAILQ_END(head)) #define TAILQ_FOREACH(var, head, field) \ for((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head); \ (var) = TAILQ_NEXT(var, field)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head); \ (var) = TAILQ_PREV(var, headname, field)) /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ (elm2)->field.tqe_next->field.tqe_prev = \ &(elm2)->field.tqe_next; \ else \ (head)->tqh_last = &(elm2)->field.tqe_next; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ *(elm2)->field.tqe_prev = (elm2); \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) /* * Circular queue definitions. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue access methods */ #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_END(head) ((void *)(head)) #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) #define CIRCLEQ_EMPTY(head) \ (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) #define CIRCLEQ_FOREACH(var, head, field) \ for((var) = CIRCLEQ_FIRST(head); \ (var) != CIRCLEQ_END(head); \ (var) = CIRCLEQ_NEXT(var, field)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for((var) = CIRCLEQ_LAST(head); \ (var) != CIRCLEQ_END(head); \ (var) = CIRCLEQ_PREV(var, field)) /* * Circular queue functions. */ #define CIRCLEQ_INIT(head) do { \ (head)->cqh_first = CIRCLEQ_END(head); \ (head)->cqh_last = CIRCLEQ_END(head); \ } while (0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm)->field.cqe_next; \ (elm)->field.cqe_prev = (listelm); \ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm); \ else \ (listelm)->field.cqe_next->field.cqe_prev = (elm); \ (listelm)->field.cqe_next = (elm); \ } while (0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ (elm)->field.cqe_next = (listelm); \ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm); \ else \ (listelm)->field.cqe_prev->field.cqe_next = (elm); \ (listelm)->field.cqe_prev = (elm); \ } while (0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ (elm)->field.cqe_next = (head)->cqh_first; \ (elm)->field.cqe_prev = CIRCLEQ_END(head); \ if ((head)->cqh_last == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm); \ else \ (head)->cqh_first->field.cqe_prev = (elm); \ (head)->cqh_first = (elm); \ } while (0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.cqe_next = CIRCLEQ_END(head); \ (elm)->field.cqe_prev = (head)->cqh_last; \ if ((head)->cqh_first == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm); \ else \ (head)->cqh_last->field.cqe_next = (elm); \ (head)->cqh_last = (elm); \ } while (0) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ (head)->cqh_last = (elm)->field.cqe_prev; \ else \ (elm)->field.cqe_next->field.cqe_prev = \ (elm)->field.cqe_prev; \ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ (head)->cqh_first = (elm)->field.cqe_next; \ else \ (elm)->field.cqe_prev->field.cqe_next = \ (elm)->field.cqe_next; \ _Q_INVALIDATE((elm)->field.cqe_prev); \ _Q_INVALIDATE((elm)->field.cqe_next); \ } while (0) #define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ CIRCLEQ_END(head)) \ (head).cqh_last = (elm2); \ else \ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ CIRCLEQ_END(head)) \ (head).cqh_first = (elm2); \ else \ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ _Q_INVALIDATE((elm)->field.cqe_prev); \ _Q_INVALIDATE((elm)->field.cqe_next); \ } while (0) #endif /* !_SYS_QUEUE_H_ */ fdm-1.7+cvs20140912/compat/strlcpy.c0000600000175000017500000000316010530106667015332 0ustar hawkhawk/* $Id: strlcpy.c,v 1.1 2006/11/19 17:00:39 nicm Exp $ */ /* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } fdm-1.7+cvs20140912/compat/tree.h0000600000175000017500000005427010647001225014577 0ustar hawkhawk/* $Id: tree.h,v 1.2 2007/07/16 23:43:17 nicm Exp $ */ /* $OpenBSD: tree.h,v 1.9 2004/11/24 18:10:42 tdeval Exp $ */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef _SYS_TREE_H_ #define _SYS_TREE_H_ /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ #define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } #define SPLAY_INITIALIZER(root) \ { NULL } #define SPLAY_INIT(root) do { \ (root)->sph_root = NULL; \ } while (0) #define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } #define SPLAY_LEFT(elm, field) (elm)->field.spe_left #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right #define SPLAY_ROOT(head) (head)->sph_root #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ #define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) #define SPLAY_LINKLEFT(head, tmp, field) do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (0) #define SPLAY_LINKRIGHT(head, tmp, field) do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (0) #define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ } while (0) /* Generates prototypes and inline functions */ #define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type * \ name##_SPLAY_FIND(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return(NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type * \ name##_SPLAY_NEXT(struct name *head, struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type * \ name##_SPLAY_MIN_MAX(struct name *head, int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ #define SPLAY_GENERATE(name, type, field, cmp) \ struct type * \ name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if(__comp < 0) { \ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type * \ name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void \ name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root))) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0){ \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } #define SPLAY_NEGINF -1 #define SPLAY_INF 1 #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) #define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) #define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) #define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); \ (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* Macros that define a red-black tree */ #define RB_HEAD(name, type) \ struct name { \ struct type *rbh_root; /* root of the tree */ \ } #define RB_INITIALIZER(root) \ { NULL } #define RB_INIT(root) do { \ (root)->rbh_root = NULL; \ } while (0) #define RB_BLACK 0 #define RB_RED 1 #define RB_ENTRY(type) \ struct { \ struct type *rbe_left; /* left element */ \ struct type *rbe_right; /* right element */ \ struct type *rbe_parent; /* parent element */ \ int rbe_color; /* node color */ \ } #define RB_LEFT(elm, field) (elm)->field.rbe_left #define RB_RIGHT(elm, field) (elm)->field.rbe_right #define RB_PARENT(elm, field) (elm)->field.rbe_parent #define RB_COLOR(elm, field) (elm)->field.rbe_color #define RB_ROOT(head) (head)->rbh_root #define RB_EMPTY(head) (RB_ROOT(head) == NULL) #define RB_SET(elm, parent, field) do { \ RB_PARENT(elm, field) = parent; \ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ RB_COLOR(elm, field) = RB_RED; \ } while (0) #define RB_SET_BLACKRED(black, red, field) do { \ RB_COLOR(black, field) = RB_BLACK; \ RB_COLOR(red, field) = RB_RED; \ } while (0) #ifndef RB_AUGMENT #define RB_AUGMENT(x) #endif #define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ (tmp) = RB_RIGHT(elm, field); \ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_LEFT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) #define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ (tmp) = RB_LEFT(elm, field); \ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_RIGHT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) /* Generates prototypes and inline functions */ #define RB_PROTOTYPE(name, type, field, cmp) \ void name##_RB_INSERT_COLOR(struct name *, struct type *); \ void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ struct type *name##_RB_REMOVE(struct name *, struct type *); \ struct type *name##_RB_INSERT(struct name *, struct type *); \ struct type *name##_RB_FIND(struct name *, struct type *); \ struct type *name##_RB_NEXT(struct type *); \ struct type *name##_RB_MINMAX(struct name *, int); \ \ /* Main rb operation. * Moves node close to the key of elm to top */ #define RB_GENERATE(name, type, field, cmp) \ void \ name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ { \ struct type *parent, *gparent, *tmp; \ while ((parent = RB_PARENT(elm, field)) && \ RB_COLOR(parent, field) == RB_RED) { \ gparent = RB_PARENT(parent, field); \ if (parent == RB_LEFT(gparent, field)) { \ tmp = RB_RIGHT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_RIGHT(parent, field) == elm) { \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_RIGHT(head, gparent, tmp, field); \ } else { \ tmp = RB_LEFT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_LEFT(parent, field) == elm) { \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_LEFT(head, gparent, tmp, field); \ } \ } \ RB_COLOR(head->rbh_root, field) = RB_BLACK; \ } \ \ void \ name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ { \ struct type *tmp; \ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ elm != RB_ROOT(head)) { \ if (RB_LEFT(parent, field) == elm) { \ tmp = RB_RIGHT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = RB_RIGHT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ struct type *oleft; \ if ((oleft = RB_LEFT(tmp, field)))\ RB_COLOR(oleft, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_RIGHT(head, tmp, oleft, field);\ tmp = RB_RIGHT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_RIGHT(tmp, field)) \ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_LEFT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } else { \ tmp = RB_LEFT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = RB_LEFT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ struct type *oright; \ if ((oright = RB_RIGHT(tmp, field)))\ RB_COLOR(oright, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_LEFT(head, tmp, oright, field);\ tmp = RB_LEFT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_LEFT(tmp, field)) \ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_RIGHT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } \ } \ if (elm) \ RB_COLOR(elm, field) = RB_BLACK; \ } \ \ struct type * \ name##_RB_REMOVE(struct name *head, struct type *elm) \ { \ struct type *child, *parent, *old = elm; \ int color; \ if (RB_LEFT(elm, field) == NULL) \ child = RB_RIGHT(elm, field); \ else if (RB_RIGHT(elm, field) == NULL) \ child = RB_LEFT(elm, field); \ else { \ struct type *left; \ elm = RB_RIGHT(elm, field); \ while ((left = RB_LEFT(elm, field))) \ elm = left; \ child = RB_RIGHT(elm, field); \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ if (RB_PARENT(elm, field) == old) \ parent = elm; \ (elm)->field = (old)->field; \ if (RB_PARENT(old, field)) { \ if (RB_LEFT(RB_PARENT(old, field), field) == old)\ RB_LEFT(RB_PARENT(old, field), field) = elm;\ else \ RB_RIGHT(RB_PARENT(old, field), field) = elm;\ RB_AUGMENT(RB_PARENT(old, field)); \ } else \ RB_ROOT(head) = elm; \ RB_PARENT(RB_LEFT(old, field), field) = elm; \ if (RB_RIGHT(old, field)) \ RB_PARENT(RB_RIGHT(old, field), field) = elm; \ if (parent) { \ left = parent; \ do { \ RB_AUGMENT(left); \ } while ((left = RB_PARENT(left, field))); \ } \ goto color; \ } \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ color: \ if (color == RB_BLACK) \ name##_RB_REMOVE_COLOR(head, parent, child); \ return (old); \ } \ \ /* Inserts a node into the RB tree */ \ struct type * \ name##_RB_INSERT(struct name *head, struct type *elm) \ { \ struct type *tmp; \ struct type *parent = NULL; \ int comp = 0; \ tmp = RB_ROOT(head); \ while (tmp) { \ parent = tmp; \ comp = (cmp)(elm, parent); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ RB_SET(elm, parent, field); \ if (parent != NULL) { \ if (comp < 0) \ RB_LEFT(parent, field) = elm; \ else \ RB_RIGHT(parent, field) = elm; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = elm; \ name##_RB_INSERT_COLOR(head, elm); \ return (NULL); \ } \ \ /* Finds the node with the same key as elm */ \ struct type * \ name##_RB_FIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (NULL); \ } \ \ struct type * \ name##_RB_NEXT(struct type *elm) \ { \ if (RB_RIGHT(elm, field)) { \ elm = RB_RIGHT(elm, field); \ while (RB_LEFT(elm, field)) \ elm = RB_LEFT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ struct type * \ name##_RB_MINMAX(struct name *head, int val) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *parent = NULL; \ while (tmp) { \ parent = tmp; \ if (val < 0) \ tmp = RB_LEFT(tmp, field); \ else \ tmp = RB_RIGHT(tmp, field); \ } \ return (parent); \ } #define RB_NEGINF -1 #define RB_INF 1 #define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) #define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) #define RB_FIND(name, x, y) name##_RB_FIND(x, y) #define RB_NEXT(name, x, y) name##_RB_NEXT(y) #define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) #define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) #define RB_FOREACH(x, name, head) \ for ((x) = RB_MIN(name, head); \ (x) != NULL; \ (x) = name##_RB_NEXT(x)) #endif /* _SYS_TREE_H_ */ fdm-1.7+cvs20140912/fetch-imappipe.c0000600000175000017500000001025510763722755015257 0ustar hawkhawk/* $Id: fetch-imappipe.c,v 1.39 2008/03/06 08:06:05 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "fdm.h" #include "fetch.h" void fetch_imappipe_fill(struct account *, struct iolist *); void fetch_imappipe_desc(struct account *, char *, size_t); int fetch_imappipe_connect(struct account *); void fetch_imappipe_disconnect(struct account *); int fetch_imappipe_putln(struct account *, const char *, va_list); int fetch_imappipe_getln(struct account *, struct fetch_ctx *, char **); int fetch_imappipe_state_init(struct account *, struct fetch_ctx *); struct fetch fetch_imappipe = { "imap", fetch_imappipe_state_init, fetch_imappipe_fill, imap_commit, /* from imap-common.c */ imap_abort, /* from imap-common.c */ imap_total, /* from imap-common.c */ fetch_imappipe_desc }; /* Write line to server. */ int fetch_imappipe_putln(struct account *a, const char *fmt, va_list ap) { struct fetch_imap_data *data = a->data; if (data->cmd->io_in == NULL) { log_warnx("%s: %s", a->name, strerror(EPIPE)); return (-1); } io_vwriteline(data->cmd->io_in, fmt, ap); return (0); } /* Get line from server. */ int fetch_imappipe_getln(struct account *a, struct fetch_ctx *fctx, char **line) { struct fetch_imap_data *data = a->data; char *out, *err, *cause; switch (cmd_poll( data->cmd, &out, &err, &fctx->lbuf, &fctx->llen, 0, &cause)) { case 0: break; case -1: log_warnx("%s: %s", a->name, cause); xfree(cause); return (-1); default: log_warnx("%s: connection unexpectedly closed", a->name); return (-1); } if (err != NULL) { log_warnx("%s: %s: %s", a->name, data->pipecmd, err); xfree(err); } *line = out; return (0); } /* Fill io list. */ void fetch_imappipe_fill(struct account *a, struct iolist *iol) { struct fetch_imap_data *data = a->data; if (data->cmd->io_in != NULL) ARRAY_ADD(iol, data->cmd->io_in); if (data->cmd->io_out != NULL) ARRAY_ADD(iol, data->cmd->io_out); if (data->cmd->io_err != NULL) ARRAY_ADD(iol, data->cmd->io_err); } /* Connect to server. */ int fetch_imappipe_connect(struct account *a) { struct fetch_imap_data *data = a->data; char *cause; data->cmd = cmd_start(data->pipecmd, CMD_IN|CMD_OUT, NULL, 0, &cause); if (data->cmd == NULL) { log_warnx("%s: %s", a->name, cause); xfree(cause); return (-1); } if (conf.debug > 3 && !conf.syslog) { data->cmd->io_in->dup_fd = STDOUT_FILENO; data->cmd->io_out->dup_fd = STDOUT_FILENO; } return (0); } /* Close connection. */ void fetch_imappipe_disconnect(struct account *a) { struct fetch_imap_data *data = a->data; if (data->cmd != NULL) cmd_free(data->cmd); } /* IMAP over pipe initial state. */ int fetch_imappipe_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_imap_data *data = a->data; data->connect = fetch_imappipe_connect; data->getln = fetch_imappipe_getln; data->putln = fetch_imappipe_putln; data->disconnect = fetch_imappipe_disconnect; data->src = NULL; return (imap_state_init(a, fctx)); } void fetch_imappipe_desc(struct account *a, char *buf, size_t len) { struct fetch_imap_data *data = a->data; char *folders; folders = fmt_strings("folders ", data->folders); if (data->user == NULL) { xsnprintf(buf, len, "imap pipe \"%s\" %s", data->pipecmd, folders); } else { xsnprintf(buf, len, "imap pipe \"%s\" user \"%s\" %s", data->pipecmd, data->user, folders); } xfree(folders); } fdm-1.7+cvs20140912/lookup.c0000600000175000017500000000272411204061551013654 0ustar hawkhawk/* $Id: lookup.c,v 1.2 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" struct userdata * user_lookup(const char *user, struct userfunctions *order) { struct userdata *ud; u_int i; for (i = 0; i < ARRAY_LENGTH(order); i++) { if ((ud = ARRAY_ITEM(order, i)(user)) != NULL) return (ud); } return (NULL); } void user_free(struct userdata *ud) { xfree(ud->name); xfree(ud->home); xfree(ud); } struct userdata * user_copy(struct userdata *ud) { struct userdata *ue; ue = xmalloc(sizeof *ue); ue->uid = ud->uid; ue->gid = ud->gid; ue->name = xstrdup(ud->name); ue->home = xstrdup(ud->home); return (ue); } fdm-1.7+cvs20140912/deliver.h0000600000175000017500000001012711030761274014004 0ustar hawkhawk/* $Id: deliver.h,v 1.25 2008/06/26 18:41:00 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef DELIVER_H #define DELIVER_H /* Deliver return codes. */ #define DELIVER_SUCCESS 0 #define DELIVER_FAILURE 1 /* Deliver context. */ struct deliver_ctx { double tim; struct action *action; struct actitem *actitem; struct rule *rule; struct account *account; struct mail *mail; struct userdata *udata; struct mail wr_mail; TAILQ_ENTRY(deliver_ctx) entry; }; /* Delivery types. */ enum delivertype { DELIVER_INCHILD,/* don't pass to parent */ DELIVER_ASUSER, /* pass to parent to drop privs */ DELIVER_WRBACK /* modifies mail: pass to parent and receive new mail */ }; /* Deliver functions. */ struct deliver { const char *name; enum delivertype type; int (*deliver)(struct deliver_ctx *, struct actitem *); void (*desc)(struct actitem *, char *, size_t); }; /* Deliver smtp states. */ enum deliver_smtp_state { SMTP_CONNECTING, SMTP_HELO, SMTP_FROM, SMTP_TO, SMTP_DATA, SMTP_DONE, SMTP_QUIT }; /* Deliver smtp data. */ struct deliver_smtp_data { struct server server; struct replstr to; struct replstr from; }; /* Deliver imap data. */ struct deliver_imap_data { char *user; char *pass; struct server server; int nocrammd5; int nologin; struct replstr folder; }; /* Deliver mbox data. */ struct deliver_mbox_data { struct replpath path; int compress; }; /* Deliver add-header data. */ struct deliver_add_header_data { struct replstr hdr; struct replstr value; }; /* Deliver remove-header data. */ struct deliver_remove_header_data { struct replstrs *hdrs; }; /* Deliver write data. */ struct deliver_write_data { struct replpath path; int append; }; /* Deliver maildir data. */ struct deliver_maildir_data { struct replpath path; }; /* Deliver rewrite data. */ struct deliver_rewrite_data { struct replpath cmd; }; /* Deliver pipe data. */ struct deliver_pipe_data { struct replpath cmd; int pipe; }; /* Deliver tag data. */ struct deliver_tag_data { struct replstr key; struct replstr value; }; /* Deliver action data. */ struct deliver_action_data { struct replstrs *actions; }; /* Deliver add-to-cache data. */ struct deliver_add_to_cache_data { char *path; struct replstr key; }; /* Deliver remove-from-cache data. */ struct deliver_remove_from_cache_data { char *path; struct replstr key; }; /* deliver-smtp.c */ extern struct deliver deliver_smtp; /* deliver-imap.c */ extern struct deliver deliver_imap; /* deliver-stdout.c */ extern struct deliver deliver_stdout; /* deliver-tag.c */ extern struct deliver deliver_tag; /* deliver-pipe.c */ extern struct deliver deliver_pipe; /* deliver-drop.c */ extern struct deliver deliver_drop; /* deliver-keep.c */ extern struct deliver deliver_keep; /* deliver-maildir.c */ extern struct deliver deliver_maildir; /* deliver-remove-header.c */ extern struct deliver deliver_remove_header; /* deliver-add-header.c */ extern struct deliver deliver_add_header; /* deliver-mbox.c */ extern struct deliver deliver_mbox; /* deliver-write.c */ extern struct deliver deliver_write; /* deliver-rewrite.c */ extern struct deliver deliver_rewrite; /* deliver-add-to-cache.c */ extern struct deliver deliver_add_to_cache; /* deliver-remove-from-cache.c */ extern struct deliver deliver_remove_from_cache; #endif fdm-1.7+cvs20140912/fetch-mbox.c0000600000175000017500000003055311561601066014407 0ustar hawkhawk/* $Id: fetch-mbox.c,v 1.13 2011/05/08 20:51:02 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" int fetch_mbox_commit(struct account *, struct mail *); void fetch_mbox_abort(struct account *); u_int fetch_mbox_total(struct account *); void fetch_mbox_desc(struct account *, char *, size_t); void fetch_mbox_free(void *); int fetch_mbox_make(struct account *); int fetch_mbox_save(struct account *, struct fetch_mbox_mbox *); int fetch_mbox_state_init(struct account *, struct fetch_ctx *); int fetch_mbox_state_next(struct account *, struct fetch_ctx *); int fetch_mbox_state_open(struct account *, struct fetch_ctx *); int fetch_mbox_state_mail(struct account *, struct fetch_ctx *); int fetch_mbox_state_exit(struct account *, struct fetch_ctx *); struct fetch fetch_mbox = { "mbox", fetch_mbox_state_init, NULL, fetch_mbox_commit, fetch_mbox_abort, NULL, fetch_mbox_desc }; void fetch_mbox_free(void *ptr) { struct fetch_mbox_mail *aux = ptr; if (aux->fmbox->reference == 0) fatalx("zero reference count"); aux->fmbox->reference--; xfree(aux); } /* Make an array of all the mboxes to visit. */ int fetch_mbox_make(struct account *a) { struct fetch_mbox_data *data = a->data; struct fetch_mbox_mbox *fmbox; char *path; u_int i, j; glob_t g; ARRAY_INIT(&data->fmboxes); for (i = 0; i < ARRAY_LENGTH(data->mboxes); i++) { path = ARRAY_ITEM(data->mboxes, i); if (glob(path, GLOB_BRACE|GLOB_NOCHECK, NULL, &g) != 0) { log_warn("%s: glob(\"%s\")", a->name, path); goto error; } if (g.gl_pathc < 1) fatalx("glob returned garbage"); for (j = 0; j < (u_int) g.gl_pathc; j++) { fmbox = xcalloc(1, sizeof *fmbox); fmbox->path = xstrdup(g.gl_pathv[j]); fmbox->fd = -1; fmbox->base = NULL; ARRAY_ADD(&data->fmboxes, fmbox); } globfree(&g); } return (0); error: for (i = 0; i < ARRAY_LENGTH(&data->fmboxes); i++) { fmbox = ARRAY_ITEM(&data->fmboxes, i); xfree(fmbox->path); xfree(fmbox); } ARRAY_FREE(&data->fmboxes); return (-1); } /* Save mbox changes. */ int fetch_mbox_save(struct account *a, struct fetch_mbox_mbox *fmbox) { struct fetch_mbox_data *data = a->data; struct fetch_mbox_mail *aux, *this; char path[MAXPATHLEN], saved[MAXPATHLEN], c; int fd; ssize_t n; struct iovec iov[2]; log_debug2("%s: %s: saving mbox: %u kept, %u total", a->name, fmbox->path, fmbox->reference, fmbox->total); fd = -1; /* * If the reference count is 0, no mails were kept, so the mbox can * just be truncated. */ if (fmbox->reference == 0) { if (fmbox->total != 0 && ftruncate(fmbox->fd, 0) != 0) goto error; goto free_all; } /* If all the mails were kept, do nothing. */ if (fmbox->reference == fmbox->total) goto free_all; /* * Otherwise, things get complicated. data->kept is a list of all the * mails (struct fetch_mbox_mail) which were kept for ALL mailboxes. * There is no guarantee it is ordered by offset. Rather than try to be * clever and save disk space, just create a new mbox and copy all the * kept mails into it. */ if (ppath(path, sizeof path, "%s.XXXXXXXXXX", fmbox->path) != 0) goto error; if (ppath(saved, sizeof saved, "%s.XXXXXXXXXX", fmbox->path) != 0) goto error; if ((fd = mkstemp(path)) == -1) goto error; aux = TAILQ_FIRST(&data->kept); while (aux != NULL) { this = aux; aux = TAILQ_NEXT(aux, entry); if (this->fmbox != fmbox) continue; log_debug2("%s: writing message from %zu, size %zu", a->name, this->off, this->size); c = '\n'; iov[0].iov_base = fmbox->base + this->off; iov[0].iov_len = this->size; iov[1].iov_base = &c; iov[1].iov_len = 1; if ((n = writev(fd, iov, 2)) < 0) goto error; if ((size_t) n != this->size + 1) { errno = EIO; goto error; } fetch_mbox_free(this); TAILQ_REMOVE(&data->kept, this, entry); } if (fsync(fd) != 0) goto error; close(fd); /* * Do the replacement dance: create a backup copy of the mbox, remove * the mbox, link in the temporary file, unlink the temporary file, * then unlink the backup mbox. We don't try to recover if anything * fails on the grounds that it could just make things worse, just * die and let the user sort it out. */ if (link(fmbox->path, saved) != 0) goto error; if (unlink(fmbox->path) != 0) goto error; if (link(path, fmbox->path) != 0) goto error; if (unlink(path) != 0) goto error; if (unlink(saved) != 0) goto error; free_all: aux = TAILQ_FIRST(&data->kept); while (aux != NULL) { this = aux; aux = TAILQ_NEXT(aux, entry); if (this->fmbox == fmbox) fetch_mbox_free(this); } if (fmbox->reference != 0) fatalx("dangling reference"); return (0); error: if (fd != -1) { close(fd); unlink(path); } log_warn("%s: %s", a->name, fmbox->path); return (-1); } /* Commit mail. */ int fetch_mbox_commit(struct account *a, struct mail *m) { struct fetch_mbox_data *data = a->data; struct fetch_mbox_mail *aux = m->auxdata; if (m->decision != DECISION_DROP) { /* * Add to kept list and reset callback to prevent free. Kept * entries are used when saving changes to mboxes. */ TAILQ_INSERT_TAIL(&data->kept, aux, entry); m->auxfree = NULL; } return (FETCH_AGAIN); } /* Abort fetching. */ void fetch_mbox_abort(struct account *a) { struct fetch_mbox_data *data = a->data; struct fetch_mbox_mbox *fmbox; u_int i; for (i = 0; i < ARRAY_LENGTH(&data->fmboxes); i++) { fmbox = ARRAY_ITEM(&data->fmboxes, i); if (fmbox->base != NULL) munmap(fmbox->base, fmbox->size); if (fmbox->fd != -1) closelock(fmbox->fd, fmbox->path, conf.lock_types); xfree(fmbox->path); xfree(fmbox); } ARRAY_FREE(&data->fmboxes); } /* Initial state. */ int fetch_mbox_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_mbox_data *data = a->data; if (fetch_mbox_make(a) != 0) return (FETCH_ERROR); if (ARRAY_EMPTY(&data->fmboxes)) { log_warnx("%s: no mboxes found", a->name); return (-1); } data->index = 0; TAILQ_INIT(&data->kept); fctx->state = fetch_mbox_state_open; return (FETCH_AGAIN); } /* Open state. */ int fetch_mbox_state_open(struct account *a, struct fetch_ctx *fctx) { struct fetch_mbox_data *data = a->data; struct fetch_mbox_mbox *fmbox; char *ptr; struct stat sb; uintmax_t size; long long used; fmbox = ARRAY_ITEM(&data->fmboxes, data->index); log_debug2("%s: trying path: %s", a->name, fmbox->path); if (stat(fmbox->path, &sb) != 0) goto error; if (S_ISDIR(sb.st_mode)) { errno = EISDIR; goto error; } if (sb.st_size == 0) { fctx->state = fetch_mbox_state_next; return (FETCH_AGAIN); } if (sb.st_size < 5) { log_warnx("%s: %s: mbox too small", a->name, fmbox->path); return (FETCH_ERROR); } size = sb.st_size; if (size > SIZE_MAX) { log_warnx("%s: %s: mbox too big", a->name, fmbox->path); return (FETCH_ERROR); } fmbox->size = size; log_debug3("%s: opening mbox, size %ju", a->name, size); used = 0; do { fmbox->fd = openlock(fmbox->path, O_RDWR, conf.lock_types); if (fmbox->fd == -1) { if (errno == EAGAIN) { if (locksleep(a->name, fmbox->path, &used) != 0) return (FETCH_ERROR); continue; } goto error; } } while (fmbox->fd < 0); /* mmap the file. */ fmbox->base = mmap( NULL, fmbox->size, PROT_READ|PROT_WRITE, MAP_SHARED, fmbox->fd, 0); madvise(fmbox->base, fmbox->size, MADV_SEQUENTIAL); if (fmbox->base == MAP_FAILED) { fmbox->base = NULL; goto error; } data->off = 0; ptr = memchr(fmbox->base, '\n', fmbox->size); if (strncmp(fmbox->base, "From ", 5) != 0) { log_warnx("%s: %s: not an mbox", a->name, fmbox->path); return (FETCH_ERROR); } fctx->state = fetch_mbox_state_mail; return (FETCH_AGAIN); error: if (fmbox->base != NULL) { munmap(fmbox->base, fmbox->size); fmbox->base = NULL; } if (fmbox->fd != -1) { closelock(fmbox->fd, fmbox->path, conf.lock_types); fmbox->fd = -1; } log_warn("%s: %s", a->name, fmbox->path); return (FETCH_ERROR); } /* Next state. Move to next mbox. */ int fetch_mbox_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_mbox_data *data = a->data; if (data->index < ARRAY_LENGTH(&data->fmboxes)) data->index++; if (data->index == ARRAY_LENGTH(&data->fmboxes)) { if (!(fctx->flags & FETCH_EMPTY)) return (FETCH_BLOCK); fctx->state = fetch_mbox_state_exit; return (FETCH_AGAIN); } fctx->state = fetch_mbox_state_open; return (FETCH_AGAIN); } /* Mail state. Find and read mail file. */ int fetch_mbox_state_mail(struct account *a, struct fetch_ctx *fctx) { struct fetch_mbox_data *data = a->data; struct mail *m = fctx->mail; struct fetch_mbox_mbox *fmbox; struct fetch_mbox_mail *aux; char *line, *ptr, *lptr; size_t llen; int flushing; /* Find current mbox and check for EOF. */ fmbox = ARRAY_ITEM(&data->fmboxes, data->index); if (data->off == fmbox->size) { fctx->state = fetch_mbox_state_next; return (FETCH_AGAIN); } /* Open the mail. */ if (mail_open(m, IO_BLOCKSIZE) != 0) { log_warn("%s: failed to create mail", a->name); mail_destroy(m); return (FETCH_ERROR); } /* Create aux data. */ aux = xmalloc(sizeof *aux); aux->off = data->off; aux->size = 0; aux->fmbox = fmbox; if (++fmbox->reference == 0) fatalx("reference count overflow"); m->auxdata = aux; m->auxfree = fetch_mbox_free; /* Tag mail. */ default_tags(&m->tags, NULL); add_tag(&m->tags, "mbox", "%s", xbasename(fmbox->path)); add_tag(&m->tags, "mbox_path", "%s", xdirname(fmbox->path)); add_tag(&m->tags, "mbox_file", "%s", fmbox->path); /* * We start at a "From " line and include it in the mail (it can be * trimmed later with minimal penalty). */ flushing = 0; for (;;) { /* Check for EOF. */ if (data->off == fmbox->size) { aux->size = data->off - aux->off; break; } /* Locate the EOL. */ line = fmbox->base + data->off; ptr = memchr(line, '\n', fmbox->size - data->off); if (ptr == NULL) { ptr = fmbox->base + fmbox->size; data->off = fmbox->size; } else data->off += ptr - line + 1; /* Check if the line is "From ". */ if (line > fmbox->base && ptr - line >= 5 && strncmp(line, "From ", 5) == 0) { /* End of mail. */ aux->size = (line - fmbox->base) - aux->off; break; } /* Trim >s from From. */ if (*line == '>') { lptr = line; llen = ptr - line; while (*lptr == '>' && llen > 0) { lptr++; llen--; } if (llen >= 5 && strncmp(lptr, "From ", 5) == 0) line++; } if (flushing) continue; if (append_line(m, line, ptr - line) != 0) { log_warn("%s: failed to resize mail", a->name); mail_destroy(m); return (FETCH_ERROR); } if (m->size > conf.max_size) flushing = 1; } fmbox->total++; /* * Check if there was a blank line between the mails and remove it if * so. */ if (aux->size >= 2 && fmbox->base[aux->off + aux->size - 1] == '\n' && fmbox->base[aux->off + aux->size - 2] == '\n') { aux->size -= 2; m->size -= 2; } return (FETCH_MAIL); } /* Clean up and free data. */ int fetch_mbox_state_exit(struct account *a, unused struct fetch_ctx *fctx) { struct fetch_mbox_data *data = a->data; u_int i; for (i = 0; i < ARRAY_LENGTH(&data->fmboxes); i++) { if (fetch_mbox_save(a, ARRAY_ITEM(&data->fmboxes, i)) != 0) return (FETCH_ERROR); } fetch_mbox_abort(a); return (FETCH_EXIT); } void fetch_mbox_desc(struct account *a, char *buf, size_t len) { struct fetch_mbox_data *data = a->data; char *mboxes; mboxes = fmt_strings("mbox ", data->mboxes); strlcpy(buf, mboxes, len); xfree(mboxes); } fdm-1.7+cvs20140912/.cvsignore0000600000175000017500000000007110676252621014204 0ustar hawkhawkfdm y.tab.c y.tab.h test.conf stress saved rfcs releases fdm-1.7+cvs20140912/array.h0000600000175000017500000000674011437312671013502 0ustar hawkhawk/* $Id: array.h,v 1.12 2010/08/31 23:52:25 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef ARRAY_H #define ARRAY_H #define ARRAY_INITIALIZER { NULL, 0, 0 } #define ARRAY_DECL(n, c) \ struct n { \ c *list; \ u_int num; \ size_t space; \ } #define ARRAY_ITEM(a, i) ((a)->list[i]) #define ARRAY_ITEMSIZE(a) (sizeof *(a)->list) #define ARRAY_INITIALSPACE(a) (10 * ARRAY_ITEMSIZE(a)) #define ARRAY_ENSURE(a, n) do { \ if (UINT_MAX - (n) < (a)->num) \ fatalx("number too big"); \ if (SIZE_MAX / ((a)->num + (n)) < ARRAY_ITEMSIZE(a)) \ fatalx("size too big"); \ if ((a)->space == 0) { \ (a)->space = ARRAY_INITIALSPACE(a); \ (a)->list = xrealloc((a)->list, 1, (a)->space); \ } \ while ((a)->space <= ((a)->num + (n)) * ARRAY_ITEMSIZE(a)) { \ (a)->list = xrealloc((a)->list, 2, (a)->space); \ (a)->space *= 2; \ } \ } while (0) #define ARRAY_EMPTY(a) (((void *) (a)) == NULL || (a)->num == 0) #define ARRAY_LENGTH(a) ((a)->num) #define ARRAY_DATA(a) ((a)->list) #define ARRAY_FIRST(a) ARRAY_ITEM(a, 0) #define ARRAY_LAST(a) ARRAY_ITEM(a, (a)->num - 1) #define ARRAY_INIT(a) do { \ (a)->num = 0; \ (a)->list = NULL; \ (a)->space = 0; \ } while (0) #define ARRAY_CLEAR(a) do { \ (a)->num = 0; \ } while (0) #define ARRAY_SET(a, i, s) do { \ (a)->list[i] = s; \ } while (0) #define ARRAY_ADD(a, s) do { \ ARRAY_ENSURE(a, 1); \ (a)->list[(a)->num] = s; \ (a)->num++; \ } while (0) #define ARRAY_INSERT(a, i, s) do { \ ARRAY_ENSURE(a, 1); \ if ((i) < (a)->num) { \ memmove((a)->list + (i) + 1, (a)->list + (i), \ ARRAY_ITEMSIZE(a) * ((a)->num - (i))); \ } \ (a)->list[i] = s; \ (a)->num++; \ } while (0) #define ARRAY_REMOVE(a, i) do { \ if ((i) < (a)->num - 1) { \ memmove((a)->list + (i), (a)->list + (i) + 1, \ ARRAY_ITEMSIZE(a) * ((a)->num - (i) - 1)); \ } \ (a)->num--; \ if ((a)->num == 0) \ ARRAY_FREE(a); \ } while (0) #define ARRAY_EXPAND(a, n) do { \ ARRAY_ENSURE(a, n); \ (a)->num += n; \ } while (0) #define ARRAY_TRUNC(a, n) do { \ if ((a)->num > n) \ (a)->num -= n; \ else \ ARRAY_FREE(a); \ } while (0) #define ARRAY_CONCAT(a, b) do { \ ARRAY_ENSURE(a, (b)->num); \ memcpy((a)->list + (a)->num, (b)->list, (b)->num * ARRAY_ITEMSIZE(a)); \ (a)->num += (b)->num; \ } while (0) #define ARRAY_FREE(a) do { \ if ((a)->list != NULL) \ xfree((a)->list); \ ARRAY_INIT(a); \ } while (0) #define ARRAY_FREEALL(a) do { \ ARRAY_FREE(a); \ xfree(a); \ } while (0) #endif fdm-1.7+cvs20140912/match-all.c0000600000175000017500000000250710665517662014227 0ustar hawkhawk/* $Id: match-all.c,v 1.1 2007/08/30 10:45:06 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_all_match(struct mail_ctx *, struct expritem *); void match_all_desc(struct expritem *, char *, size_t); struct match match_all = { "all", match_all_match, match_all_desc }; int match_all_match(unused struct mail_ctx *mctx, unused struct expritem *ei) { return (MATCH_TRUE); } void match_all_desc(unused struct expritem *ei, char *buf, size_t len) { strlcpy(buf, "all", len); } fdm-1.7+cvs20140912/command.c0000600000175000017500000002434711206610753013774 0ustar hawkhawk/* $Id: command.c,v 1.55 2009/05/25 21:47:23 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "fdm.h" #define CMD_DEBUG(io, fmt, ...) #ifndef CMD_DEBUG #define CMD_DEBUG(cmt, fmt, ...) \ log_debug3("%s: (%d) " fmt, __func__, cmd->pid, ## __VA_ARGS__) #endif /* Start a command. */ struct cmd * cmd_start(const char *s, int flags, const char *buf, size_t len, char **cause) { struct cmd *cmd; int fd_in[2], fd_out[2], fd_err[2]; cmd = xmalloc(sizeof *cmd); cmd->pid = -1; cmd->flags = flags; if (buf != NULL && len != 0 && flags & CMD_IN) { cmd->buf = buf; cmd->len = len; } else { cmd->buf = NULL; cmd->len = 0; } fd_in[0] = fd_in[1] = -1; fd_out[0] = fd_out[1] = -1; fd_err[0] = fd_err[1] = -1; /* Open child's stdin. */ if (flags & CMD_IN) { if (pipe(fd_in) != 0) { xasprintf(cause, "pipe: %s", strerror(errno)); goto error; } } else { fd_in[0] = open(_PATH_DEVNULL, O_RDONLY, 0); if (fd_in[0] < 0) { xasprintf(cause, "open: %s", strerror(errno)); goto error; } } /* Open child's stdout. */ if (flags & CMD_OUT) { if (pipe(fd_out) != 0) { xasprintf(cause, "pipe: %s", strerror(errno)); goto error; } } else { fd_out[1] = open(_PATH_DEVNULL, O_WRONLY, 0); if (fd_out[1] < 0) { xasprintf(cause, "open: %s", strerror(errno)); goto error; } } /* Open child's stderr. */ if (pipe(fd_err) != 0) { xasprintf(cause, "pipe: %s", strerror(errno)); goto error; } /* Fork the child. */ switch (cmd->pid = fork()) { case -1: xasprintf(cause, "fork: %s", strerror(errno)); goto error; case 0: /* Child. */ cmd->pid = getpid(); CMD_DEBUG(cmd, "started (child)"); if (fd_in[1] != -1) close(fd_in[1]); if (fd_out[0] != -1) close(fd_out[0]); close(fd_err[0]); if (dup2(fd_in[0], STDIN_FILENO) == -1) fatal("dup2(stdin) failed"); close(fd_in[0]); if (dup2(fd_out[1], STDOUT_FILENO) == -1) fatal("dup2(stdout) failed"); close(fd_out[1]); if (dup2(fd_err[1], STDERR_FILENO) == -1) fatal("dup2(stderr) failed"); close(fd_err[1]); #ifdef SIGINFO if (signal(SIGINFO, SIG_DFL) == SIG_ERR) fatal("signal failed"); #endif if (signal(SIGUSR1, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGINT, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGTERM, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGUSR1, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGUSR2, SIG_DFL) == SIG_ERR) fatal("signal failed"); if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) fatal("signal failed"); execl(_PATH_BSHELL, "sh", "-c", s, (char *) NULL); fatal("execl failed"); } CMD_DEBUG(cmd, "started (parent)"); /* XXX Check if the child has actually started. */ if (kill(cmd->pid, 0) != 0) { if (errno == ESRCH) CMD_DEBUG(cmd, "child not running"); else if (errno != EPERM) fatal("kill"); } /* Parent. */ close(fd_in[0]); fd_in[0] = -1; close(fd_out[1]); fd_out[1] = -1; close(fd_err[1]); fd_err[1] = -1; /* Create ios. */ cmd->io_in = NULL; if (fd_in[1] != -1) { cmd->io_in = io_create(fd_in[1], NULL, IO_LF); io_writeonly(cmd->io_in); if (cmd->len != 0) cmd->io_in->flags |= IOF_MUSTWR; } cmd->io_out = NULL; if (fd_out[0] != -1) { cmd->io_out = io_create(fd_out[0], NULL, IO_LF); io_readonly(cmd->io_out); } cmd->io_err = io_create(fd_err[0], NULL, IO_LF); io_readonly(cmd->io_err); return (cmd); error: if (cmd->pid != -1) kill(cmd->pid, SIGTERM); if (fd_in[0] != -1) close(fd_in[0]); if (fd_in[1] != -1) close(fd_in[1]); if (fd_out[0] != -1) close(fd_out[0]); if (fd_out[1] != -1) close(fd_out[1]); if (fd_err[0] != -1) close(fd_err[0]); if (fd_err[1] != -1) close(fd_err[1]); xfree(cmd); return (NULL); } /* * Poll a command. Returns -1 on error, 0 if output is found, or the child's * return code + 1 if it has exited. */ int cmd_poll(struct cmd *cmd, char **out, char **err, char **lbuf, size_t *llen, int timeout, char **cause) { struct io *io, *ios[3]; size_t len; ssize_t n; pid_t pid; int flags; CMD_DEBUG(cmd, "in=%p, out=%p, err=%p", cmd->io_in, cmd->io_out, cmd->io_err); /* Reset return pointers. */ if (err != NULL) *err = NULL; if (out != NULL) *out = NULL; /* * Handle fixed buffer. We can't just write everything in cmd_start * as the child may block waiting for us to read. So, write as much * as possible here while still polling the others. If CMD_ONCE is set * stdin is closed when the buffer is done. */ if (cmd->len != 0 && cmd->io_in != NULL && !IO_CLOSED(cmd->io_in)) { CMD_DEBUG(cmd, "writing, %zu left", cmd->len); n = write(cmd->io_in->fd, cmd->buf, cmd->len); CMD_DEBUG(cmd, "write returned %zd (errno=%d)", n, errno); switch (n) { case 0: errno = EPIPE; /* FALLTHROUGH */ case -1: if (errno == EINTR || errno == EAGAIN) break; /* * Ignore closed input, rely on child returning non- * zero on error and caller checking before writing to * it. */ if (errno == EPIPE) { cmd->len = 0; break; } xasprintf(cause, "write: %s", strerror(errno)); return (-1); default: cmd->buf += n; cmd->len -= n; break; } if (cmd->len == 0) { if (cmd->flags & CMD_ONCE) { CMD_DEBUG(cmd, "write finished, closing"); io_close(cmd->io_in); io_free(cmd->io_in); cmd->io_in = NULL; } else { CMD_DEBUG(cmd, "write finished"); cmd->io_in->flags &= ~IOF_MUSTWR; } } } /* No lines available. If there is anything open, try and poll it. */ if (cmd->io_in != NULL || cmd->io_out != NULL || cmd->io_err != NULL) { ios[0] = cmd->io_in; ios[1] = cmd->io_out; ios[2] = cmd->io_err; CMD_DEBUG(cmd, "polling, timeout=%d", timeout); switch (io_polln(ios, 3, &io, timeout, cause)) { case -1: CMD_DEBUG(cmd, "poll error: %s", strerror(errno)); if (errno == EAGAIN) break; kill(cmd->pid, SIGTERM); return (-1); case 0: CMD_DEBUG(cmd, "poll closed"); kill(cmd->pid, SIGTERM); /* * Check for closed. It'd be nice for closed input to * be an error, but we can't tell the difference * between error and normal child exit, so just free it * and rely on the caller to handle it. */ if (io == cmd->io_in) { CMD_DEBUG(cmd, "closing in"); io_close(cmd->io_in); io_free(cmd->io_in); cmd->io_in = NULL; } if (io == cmd->io_out && IO_RDSIZE(cmd->io_out) == 0) { CMD_DEBUG(cmd, "closing out"); io_close(cmd->io_out); io_free(cmd->io_out); cmd->io_out = NULL; } if (io == cmd->io_err && IO_RDSIZE(cmd->io_err) == 0) { CMD_DEBUG(cmd, "closing err"); io_close(cmd->io_err); io_free(cmd->io_err); cmd->io_err = NULL; } break; } } CMD_DEBUG(cmd, "poll out"); /* * Retrieve and return a line if possible. This must be after the * poll otherwise it'll get screwed up by external poll, like so: * - no data buffered so test for line finds nothing * - all sockets polled here, the data being waited for has * arrived, it is read and buffered and the function returns * - the external poll blocks, but since the data being waited * on has already arrived, doesn't wake up * Maybe an EXTERNALPOLL flag to eliminate the double-poll would clear * things up? Just an IO_CLOSED check here... */ if (cmd->io_err != NULL) { CMD_DEBUG(cmd, "err has %zu bytes", IO_RDSIZE(cmd->io_err)); *err = io_readline2(cmd->io_err, lbuf, llen); if (*err != NULL) { /* Strip CR if the line is terminated by one. */ len = strlen(*err); if (len > 0 && (*err)[len - 1] == '\r') (*err)[len - 1] = '\0'; return (0); } } if (cmd->io_out != NULL) { CMD_DEBUG(cmd, "out has %zu bytes", IO_RDSIZE(cmd->io_out)); *out = io_readline2(cmd->io_out, lbuf, llen); if (*out != NULL) { /* Strip CR if the line is terminated by one. */ len = strlen(*out); if (len > 0 && (*out)[len - 1] == '\r') (*out)[len - 1] = '\0'; return (0); } } /* If anything is still open, return now and don't check the child. */ if (cmd->io_in != NULL || cmd->io_out != NULL || cmd->io_err != NULL) return (0); /* Everything is closed. Check the child. */ CMD_DEBUG(cmd, "waiting for child, timeout=%d", timeout); flags = WNOHANG; if (timeout != 0) { flags = 0; timer_set(timeout / 1000); } pid = waitpid(cmd->pid, &cmd->status, flags); if (timeout != 0) timer_cancel(); if (pid == -1) { if (timeout != 0 && errno == EINTR && timer_expired()) errno = ETIMEDOUT; xasprintf(cause, "waitpid: %s", strerror(errno)); return (-1); } if (pid == 0) return (0); /* Child is dead, sort out what to return. */ CMD_DEBUG(cmd, "child exited, status=%d", cmd->status); cmd->pid = -1; if (WIFSIGNALED(cmd->status)) { xasprintf(cause, "child got signal: %d", WTERMSIG(cmd->status)); return (-1); } if (!WIFEXITED(cmd->status)) { xasprintf(cause, "child didn't exit normally"); return (-1); } cmd->status = WEXITSTATUS(cmd->status); return (1 + cmd->status); } void cmd_free(struct cmd *cmd) { if (cmd->pid != -1) kill(cmd->pid, SIGTERM); if (cmd->io_in != NULL) { io_close(cmd->io_in); io_free(cmd->io_in); } if (cmd->io_out != NULL) { io_close(cmd->io_out); io_free(cmd->io_out); } if (cmd->io_err != NULL) { io_close(cmd->io_err); io_free(cmd->io_err); } xfree(cmd); } fdm-1.7+cvs20140912/replace.c0000600000175000017500000001576411204061551013766 0ustar hawkhawk/* $Id: replace.c,v 1.49 2009/05/17 19:20:09 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "fdm.h" #define ALIAS_IDX(ch) /* LINTED */ \ (((ch) >= 'a' && (ch) <= 'z') ? (ch) - 'a' : \ (((ch) >= 'A' && (ch) <= 'Z') ? 26 + (ch) - 'A' : -1)) static const char *aliases[] = { "account", /* a */ NULL, /* b */ NULL, /* c */ "day", /* d */ NULL, /* e */ NULL, /* f */ NULL, /* g */ "home", /* h */ NULL, /* i */ NULL, /* j */ NULL, /* l */ NULL, /* l */ "month", /* m */ "uid", /* n */ NULL, /* o */ NULL, /* p */ NULL, /* q */ NULL, /* r */ "source", /* s */ "action", /* t */ "user", /* u */ NULL, /* v */ NULL, /* w */ NULL, /* x */ "year", /* y */ NULL, /* z */ NULL, /* A */ NULL, /* B */ NULL, /* C */ NULL, /* D */ NULL, /* E */ NULL, /* F */ NULL, /* G */ "hour", /* H */ NULL, /* I */ NULL, /* J */ NULL, /* K */ NULL, /* L */ "minute", /* M */ NULL, /* N */ NULL, /* O */ NULL, /* P */ "quarter", /* Q */ NULL, /* R */ "second", /* S */ NULL, /* T */ NULL, /* U */ NULL, /* V */ "dayofweek", /* W */ NULL, /* X */ "dayofyear", /* Y */ NULL, /* Z */ }; char *replace(char *, struct strb *, struct mail *, struct rmlist *); const char *submatch(char, struct mail *, struct rmlist *, size_t *); void printflike3 add_tag(struct strb **tags, const char *key, const char *value, ...) { va_list ap; va_start(ap, value); strb_vadd(tags, key, value, ap); va_end(ap); } const char * find_tag(struct strb *tags, const char *key) { struct strbent *sbe; sbe = strb_find(tags, key); if (sbe == NULL) return (NULL); return (STRB_VALUE(tags, sbe)); } const char * match_tag(struct strb *tags, const char *pattern) { struct strbent *sbe; sbe = strb_match(tags, pattern); if (sbe == NULL) return (NULL); return (STRB_VALUE(tags, sbe)); } void default_tags(struct strb **tags, const char *src) { char rtime[128]; struct tm *tm; time_t t; strb_clear(tags); if (src != NULL) add_tag(tags, "source", "%s", src); if (conf.host_name != NULL) add_tag(tags, "hostname", "%s", conf.host_name); t = time(NULL); if ((tm = localtime(&t)) != NULL) { /* * Okay, in a struct tm, everything is zero-based (including * month!) except day of the month which is one-based. * * To make thing clearer, strftime(3) measures everything as * you would expect... except that day of the week runs from * 0-6 but day of the year runs from 1-366. * * Fun fun fun. */ add_tag(tags, "hour", "%.2d", tm->tm_hour); add_tag(tags, "minute", "%.2d", tm->tm_min); add_tag(tags, "second", "%.2d", tm->tm_sec); add_tag(tags, "day", "%.2d", tm->tm_mday); add_tag(tags, "month", "%.2d", tm->tm_mon + 1); add_tag(tags, "year", "%.4d", 1900 + tm->tm_year); add_tag(tags, "year2", "%.2d", tm->tm_year % 100); add_tag(tags, "dayofweek", "%d", tm->tm_wday); add_tag(tags, "dayofyear", "%.2d", tm->tm_yday + 1); add_tag(tags, "quarter", "%d", tm->tm_mon / 3 + 1); } if (rfc822time(t, rtime, sizeof rtime) != NULL) add_tag(tags, "rfc822date", "%s", rtime); } void update_tags(struct strb **tags, struct userdata *ud) { add_tag(tags, "user", "%s", ud->name); add_tag(tags, "home", "%s", ud->home); add_tag(tags, "uid", "%lu", (u_long) ud->uid); add_tag(tags, "gid", "%lu", (u_long) ud->gid); } void reset_tags(struct strb **tags) { add_tag(tags, "user", "%s", ""); add_tag(tags, "home", "%s", ""); add_tag(tags, "uid", "%s", ""); add_tag(tags, "gid", "%s", ""); } char * replacestr(struct replstr *rs, struct strb *tags, struct mail *m, struct rmlist *rml) { return (replace(rs->str, tags, m, rml)); } char * replacepath(struct replpath *rp, struct strb *tags, struct mail *m, struct rmlist *rml, const char *home) { char *s, *t; s = replace(rp->str, tags, m, rml); if ((t = expand_path(s, home)) == NULL) return (s); xfree(s); return (t); } const char * submatch(char ch, struct mail *m, struct rmlist *rml, size_t *len) { struct rm *rm; if (rml == NULL || !rml->valid || m == NULL) return (NULL); rm = &rml->list[((u_char) ch) - '0']; if (!rm->valid) return (NULL); *len = rm->eo - rm->so; return (m->data + rm->so); } char * replace(char *src, struct strb *tags, struct mail *m, struct rmlist *rml) { const char *tptr, *alias; char *ptr, *tend, *dst, ch; size_t i, off, len, tlen; int strip; if (src == NULL) return (NULL); if (*src == '\0') return (xstrdup("")); off = 0; len = REPLBUFSIZE; dst = xmalloc(len); strip = 1; for (ptr = src; *ptr != '\0'; ptr++) { switch (*ptr) { case '%': break; default: ENSURE_FOR(dst, len, off, 1); dst[off++] = *ptr; continue; } switch (ch = *++ptr) { case '\0': goto out; case '%': ENSURE_FOR(dst, len, off, 1); dst[off++] = '%'; continue; case '[': if ((tend = strchr(ptr, ']')) == NULL) { ENSURE_FOR(dst, len, off, 2); dst[off++] = '%'; dst[off++] = '['; continue; } ptr++; if (*ptr == ':') { strip = 0; ptr++; } if (ptr == tend) continue; *tend = '\0'; if ((tptr = find_tag(tags, ptr)) == NULL) { *tend = ']'; ptr = tend; continue; } tlen = strlen(tptr); *tend = ']'; ptr = tend; break; case ':': ch = *++ptr; if (ch >= '0' && ch <= '9') { tptr = submatch(ch, m, rml, &tlen); if (tptr == NULL) continue; strip = 0; break; } ENSURE_FOR(dst, len, off, 1); dst[off++] = ch; continue; default: if (ch >= '0' && ch <= '9') { tptr = submatch(ch, m, rml, &tlen); if (tptr == NULL) continue; break; } alias = NULL; if (ALIAS_IDX((u_char) ch) != -1) alias = aliases[ALIAS_IDX((u_char) ch)]; if (alias == NULL) continue; if ((tptr = find_tag(tags, alias)) == NULL) continue; tlen = strlen(tptr); break; } if (tlen == 0) continue; ENSURE_FOR(dst, len, off, tlen); if (!strip) { memcpy(dst + off, tptr, tlen); off += tlen; continue; } for (i = 0; i < tlen; i++) { if (strchr(conf.strip_chars, tptr[i]) == NULL) dst[off++] = tptr[i]; } } out: ENSURE_FOR(dst, len, off, 1); dst[off] = '\0'; return (dst); } fdm-1.7+cvs20140912/cleanup.c0000600000175000017500000000610510651743055014002 0ustar hawkhawk/* $Id: cleanup.c,v 1.11 2007/07/25 21:52:45 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "fdm.h" struct cleanent { char *path; TAILQ_ENTRY(cleanent) entry; }; TAILQ_HEAD(, cleanent) cleanlist; void cleanup_check(void) { struct cleanent *cent; if (!TAILQ_EMPTY(&cleanlist)) { TAILQ_FOREACH(cent, &cleanlist, entry) log_debug("cleanup: %s", cent->path); fatalx("list not empty"); } } void cleanup_purge(void) { struct cleanent *cent; int saved_errno; /* * This must be signal safe. */ saved_errno = errno; TAILQ_FOREACH(cent, &cleanlist, entry) { if (unlink(cent->path) != 0) { write(STDERR_FILENO, "unlink failed\n", 14); _exit(1); } } errno = saved_errno; } void cleanup_flush(void) { sigset_t set, oset; struct cleanent *cent; sigfillset(&set); if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) fatal("sigprocmask failed"); while (!TAILQ_EMPTY(&cleanlist)) { cent = TAILQ_FIRST(&cleanlist); TAILQ_REMOVE(&cleanlist, cent, entry); xfree(cent->path); xfree(cent); } if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) fatal("sigprocmask failed"); } void cleanup_register(const char *path) { sigset_t set, oset; struct cleanent *cent; #if 0 log_debug("cleanup_register: %s by %ld", path, (long) getpid()); #endif if (path == NULL || *path == '\0') fatalx("empty path"); cent = xmalloc(sizeof *cent); cent->path = xstrdup(path); sigfillset(&set); if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) fatal("sigprocmask failed"); TAILQ_INSERT_HEAD(&cleanlist, cent, entry); if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) fatal("sigprocmask failed"); } void cleanup_deregister(const char *path) { sigset_t set, oset; struct cleanent *cent; #if 0 log_debug("cleanup_deregister: %s by %ld", path, (long) getpid()); #endif if (path == NULL || *path == '\0') fatalx("empty path"); sigfillset(&set); if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) fatal("sigprocmask failed"); TAILQ_FOREACH(cent, &cleanlist, entry) { if (strcmp(cent->path, path) == 0) { TAILQ_REMOVE(&cleanlist, cent, entry); xfree(cent->path); xfree(cent); goto out; } } fatalx("entry not found"); out: if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) fatal("sigprocmask failed"); } fdm-1.7+cvs20140912/deliver-keep.c0000600000175000017500000000267510602544475014740 0ustar hawkhawk/* $Id: deliver-keep.c,v 1.8 2007/03/28 19:59:57 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_keep_deliver(struct deliver_ctx *, struct actitem *); void deliver_keep_desc(struct actitem *, char *, size_t); struct deliver deliver_keep = { "keep", DELIVER_INCHILD, deliver_keep_deliver, deliver_keep_desc }; int deliver_keep_deliver(struct deliver_ctx *dctx, unused struct actitem *ti) { struct mail *m = dctx->mail; m->decision = DECISION_KEEP; return (DELIVER_SUCCESS); } void deliver_keep_desc(unused struct actitem *ti, char *buf, size_t len) { strlcpy(buf, "keep", len); } fdm-1.7+cvs20140912/xmalloc.c0000600000175000017500000001042311047077133014005 0ustar hawkhawk/* $Id: xmalloc.c,v 1.48 2008/08/08 17:11:55 nicm Exp $ */ /* * Copyright (c) 2004 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "fdm.h" void * ensure_for(void *buf, size_t *len, size_t size, size_t adj) { if (adj == 0) fatalx("zero adj"); if (SIZE_MAX - size < adj) fatalx("size + adj > SIZE_MAX"); size += adj; if (*len == 0) { *len = BUFSIZ; buf = xmalloc(*len); } while (*len <= size) { buf = xrealloc(buf, 2, *len); *len *= 2; } return (buf); } void * ensure_size(void *buf, size_t *len, size_t nmemb, size_t size) { if (nmemb == 0 || size == 0) fatalx("zero size"); if (SIZE_MAX / nmemb < size) fatalx("nmemb * size > SIZE_MAX"); if (*len == 0) { *len = BUFSIZ; buf = xmalloc(*len); } while (*len <= nmemb * size) { buf = xrealloc(buf, 2, *len); *len *= 2; } return (buf); } char * xstrdup(const char *s) { void *ptr; size_t len; len = strlen(s) + 1; ptr = xmalloc(len); return (strncpy(ptr, s, len)); } void * xcalloc(size_t nmemb, size_t size) { void *ptr; if (size == 0 || nmemb == 0) fatalx("zero size"); if (SIZE_MAX / nmemb < size) fatalx("nmemb * size > SIZE_MAX"); if ((ptr = calloc(nmemb, size)) == NULL) fatal("xcalloc failed"); #ifdef DEBUG xmalloc_new(xmalloc_caller(), ptr, nmemb * size); #endif return (ptr); } void * xmalloc(size_t size) { void *ptr; if (size == 0) fatalx("zero size"); if ((ptr = malloc(size)) == NULL) fatal("xmalloc failed"); #ifdef DEBUG xmalloc_new(xmalloc_caller(), ptr, size); #endif return (ptr); } void * xrealloc(void *oldptr, size_t nmemb, size_t size) { size_t newsize = nmemb * size; void *newptr; if (newsize == 0) fatalx("zero size"); if (SIZE_MAX / nmemb < size) fatalx("nmemb * size > SIZE_MAX"); if ((newptr = realloc(oldptr, newsize)) == NULL) fatal("xrealloc failed"); #ifdef DEBUG xmalloc_change(xmalloc_caller(), oldptr, newptr, nmemb * size); #endif return (newptr); } void xfree(void *ptr) { if (ptr == NULL) fatalx("null pointer"); free(ptr); #ifdef DEBUG xmalloc_free(ptr); #endif } int printflike2 xasprintf(char **ret, const char *fmt, ...) { va_list ap; int i; va_start(ap, fmt); i = xvasprintf(ret, fmt, ap); va_end(ap); return (i); } int xvasprintf(char **ret, const char *fmt, va_list ap) { int i; i = vasprintf(ret, fmt, ap); if (i < 0 || *ret == NULL) fatal("xvasprintf failed"); #ifdef DEBUG xmalloc_new(xmalloc_caller(), *ret, i + 1); #endif return (i); } int printflike3 xsnprintf(char *buf, size_t len, const char *fmt, ...) { va_list ap; int i; va_start(ap, fmt); i = xvsnprintf(buf, len, fmt, ap); va_end(ap); return (i); } int xvsnprintf(char *buf, size_t len, const char *fmt, va_list ap) { int i; if (len > INT_MAX) fatalx("len > INT_MAX"); i = vsnprintf(buf, len, fmt, ap); if (i < 0) fatal("vsnprintf failed"); return (i); } /* * Some systems modify the path in place. This function and xbasename below * avoid that by using a temporary buffer. */ char * xdirname(const char *src) { static char dst[MAXPATHLEN]; strlcpy(dst, src, sizeof dst); return (dirname(dst)); } char * xbasename(const char *src) { static char dst[MAXPATHLEN]; strlcpy(dst, src, sizeof dst); return (basename(dst)); } fdm-1.7+cvs20140912/deliver-add-header.c0000600000175000017500000000470210641135454015757 0ustar hawkhawk/* $Id: deliver-add-header.c,v 1.17 2007/06/29 07:56:28 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_add_header_deliver(struct deliver_ctx *, struct actitem *); void deliver_add_header_desc(struct actitem *, char *, size_t); struct deliver deliver_add_header = { "add-header", DELIVER_INCHILD, deliver_add_header_deliver, deliver_add_header_desc }; int deliver_add_header_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_add_header_data *data = ti->data; char *hdr, *value = NULL; hdr = replacestr(&data->hdr, m->tags, m, &m->rml); if (hdr == NULL || *hdr == '\0') { log_warnx("%s: empty header", a->name); goto error; } value = replacestr(&data->value, m->tags, m, &m->rml); if (value == NULL) { log_warnx("%s: bad value for header %s", a->name, hdr); goto error; } log_debug2("%s: adding header: %s", a->name, hdr); if (insert_header(m, NULL, "%s: %s", hdr, value) != 0) { log_warnx("%s: failed to add header %s (%s)", a->name, hdr, value); goto error; } ARRAY_FREE(&m->wrapped); m->wrapchar = '\0'; fill_wrapped(m); /* Invalidate the match data since stuff may have moved. */ m->rml.valid = 0; xfree(hdr); xfree(value); return (DELIVER_SUCCESS); error: if (hdr != NULL) xfree(hdr); if (value != NULL) xfree(value); return (DELIVER_FAILURE); } void deliver_add_header_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_add_header_data *data = ti->data; xsnprintf(buf, len, "add-header \"%s\" value \"%s\"", data->hdr.str, data->value.str); } fdm-1.7+cvs20140912/deliver-remove-header.c0000600000175000017500000000557710665517662016552 0ustar hawkhawk/* $Id: deliver-remove-header.c,v 1.19 2007/08/30 10:45:06 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "deliver.h" int deliver_remove_header_deliver(struct deliver_ctx *, struct actitem *); void deliver_remove_header_desc(struct actitem *, char *, size_t); struct deliver deliver_remove_header = { "remove-header", DELIVER_INCHILD, deliver_remove_header_deliver, deliver_remove_header_desc }; int deliver_remove_header_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_remove_header_data *data = ti->data; char *ptr, *hdr; size_t len, off, wrap; u_int i, j; for (j = 0; j < ARRAY_LENGTH(data->hdrs); j++) { hdr = replacestr( &ARRAY_ITEM(data->hdrs, j), m->tags, m, &m->rml); if (hdr == NULL || *hdr == '\0') { if (hdr != NULL) xfree(hdr); log_warnx("%s: empty header", a->name); return (DELIVER_FAILURE); } log_debug2("%s: removing header: %s", a->name, hdr); ARRAY_FREE(&m->wrapped); m->wrapchar = '\0'; fill_wrapped(m); set_wrapped(m, ' '); while ((ptr = match_header(m, hdr, &len, 0)) != NULL) { log_debug3("%s: found header to remove: %.*s", a->name, (int) len, ptr); /* Remove the header. */ memmove( ptr, ptr + len, m->size - len - (ptr - m->data)); m->size -= len; m->body -= len; /* Fix up the wrapped array. */ off = ptr - m->data; i = 0; while (i < ARRAY_LENGTH(&m->wrapped)) { wrap = ARRAY_ITEM(&m->wrapped, i); if (wrap >= off + len) { ARRAY_SET(&m->wrapped, i, wrap - len); i++; } else if (wrap >= off) ARRAY_REMOVE(&m->wrapped, i); else i++; } } } /* Invalidate the match data since stuff may have moved. */ m->rml.valid = 0; set_wrapped(m, '\n'); return (DELIVER_SUCCESS); } void deliver_remove_header_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_remove_header_data *data = ti->data; char *hdrs; hdrs = fmt_replstrs("remove-header ", data->hdrs); strlcpy(buf, hdrs, len); xfree(hdrs); } fdm-1.7+cvs20140912/timer.c0000600000175000017500000000432610664377317013506 0ustar hawkhawk/* $Id: timer.c,v 1.3 2007/08/26 22:29:35 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "fdm.h" volatile sig_atomic_t timer_value; void timer_handler(int); /* Signal handler for SIGALRM setitimer timeout. */ void timer_handler(unused int sig) { timer_value = 1; } /* Return timer state. */ int timer_expired(void) { return (timer_value); } /* Set timer with setitimer. */ void timer_set(int seconds) { struct itimerval itv; struct sigaction act; if (seconds == 0) fatalx("zero timeout"); timer_value = 0; memset(&act, 0, sizeof act); sigemptyset(&act.sa_mask); act.sa_handler = timer_handler; if (sigaction(SIGALRM, &act, NULL) != 0) fatal("sigaction failed"); memset(&itv, 0, sizeof itv); itv.it_value.tv_sec = seconds; while (setitimer(ITIMER_REAL, &itv, NULL) != 0) { /* * If the timeout is too large (EINVAL), keep trying it until * it reaches a minimum of 30 seconds. */ if (errno != EINVAL || itv.it_value.tv_sec < 30) fatal("setitimer failed"); itv.it_value.tv_sec /= 2; } } /* Unset timer. */ void timer_cancel(void) { struct itimerval itv; struct sigaction act; memset(&itv, 0, sizeof itv); if (setitimer(ITIMER_REAL, &itv, NULL) != 0) fatal("setitimer failed"); memset(&act, 0, sizeof act); sigemptyset(&act.sa_mask); act.sa_handler = SIG_DFL; if (sigaction(SIGALRM, &act, NULL) != 0) fatal("sigaction failed"); } fdm-1.7+cvs20140912/examples/0000700000175000017500000000000012404656670014025 5ustar hawkhawkfdm-1.7+cvs20140912/examples/t-ulmer.conf0000600000175000017500000001430510676140530016256 0ustar hawkhawkaccount "tmux" imaps server "host" user "tobiasu" pass "password" account "spam-box" maildir "%h/mail/.spam.train" # Actions action "inbox" maildir "%h/mail" action "friends" maildir "%h/mail/.friends" action "tendra-dev" maildir "%h/mail/.tendra-dev" action "edri" maildir "%h/mail/.edri" action "fitug" maildir "%h/mail/.fitug" action "nanog" maildir "%h/mail/.nanog" action "cryptogram" maildir "%h/mail/.cryptogram" action "bibliothek" maildir "%h/mail/.bibliothek" action "arcor" maildir "%h/mail/.arcor" action "obsd-advocacy" maildir "%h/mail/.openbsd.advocacy" action "obsd-alpha" maildir "%h/mail/.openbsd.alpha" action "obsd-arm" maildir "%h/mail/.openbsd.arm" action "obsd-bugs" maildir "%h/mail/.openbsd.bugs" action "obsd-hppa" maildir "%h/mail/.openbsd.hppa" action "obsd-ipv6" maildir "%h/mail/.openbsd.ipv6" action "obsd-mac68k" maildir "%h/mail/.openbsd.mac68k" action "obsd-misc" maildir "%h/mail/.openbsd.misc" action "obsd-ports" maildir "%h/mail/.openbsd.ports" action "obsd-ports-changes" maildir "%h/mail/.openbsd.ports-changes" action "obsd-ppc" maildir "%h/mail/.openbsd.ppc" action "obsd-source-changes" maildir "%h/mail/.openbsd.source-changes" action "obsd-sparc" maildir "%h/mail/.openbsd.sparc" action "obsd-tech" maildir "%h/mail/.openbsd.tech" action "obsd-vax" maildir "%h/mail/.openbsd.vax" action "obsd-www" maildir "%h/mail/.openbsd.www" action "obsd-x11" maildir "%h/mail/.openbsd.x11" action "sec-bugtraq" maildir "%h/mail/.security.bugtraq" action "sec-fd" maildir "%h/mail/.security.full-disclosure" action "sec-secunia" maildir "%h/mail/.security.secunia" action "suck-wmii" maildir "%h/mail/.suckless.wmii" action "suck-dwm" maildir "%h/mail/.suckless.dwm" action "spam-new" maildir "%h/mail/.spam.new" action "spam-train" maildir "%h/mail/.spam.train" action "spam-archive" mbox "%h/mail/archive/trained-spam" action "drop" drop action "bf-unreg-nospam" rewrite "bogofilter -e -p -N" action "bf-reg-spam" rewrite "bogofilter -e -p -s" action "bf-update" rewrite "bogofilter -e -p -u" action "strip-bullshit" rewrite "reformail -IX-HE-Virus-Scanned: -IX-HE-Spam-Level: -IX-HE-Spam-Score: -IX-HE-Spam-Report: -IX-Antivirus-Scanner: -IX-AntiAbuse: -IX-Spam: -IX-SPAM-FLAG: -IX-Virus-Scanned: -IX-ELNK-Trace: -IX-Converted-To-Plain-Text: -IX-Priority: -IX-Greylist: -IX-OriginalArrivalTime: -IX-Sun-Charset: -IX-MSMail-Priority: -IImportance: -IX-MimeOLE: -IPriority:" action "strip-fd" rewrite "sed 's/^\\(Subject:.*\\)\\[Full-disclosure\\] /\\1/'" # spam-box rules match account "spam-box" { match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match "^X-MySecretSpamHeader: No" in headers action "bf-unreg-nospam" continue match all action "bf-reg-spam" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match all action "bf-reg-spam" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match all action "bf-reg-spam" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match all action "bf-reg-spam" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match all action "bf-reg-spam" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-archive" match all action "bf-reg-spam" continue # ok, we've tried 5 times, just put the thing into the spam archive match all action "spam-archive" } # tmux rules match all action "strip-bullshit" continue match all action "bf-update" continue match "^X-MySecretSpamHeader: Yes" in headers action "spam-new" match "^sender: owner.*@openbsd\\.org" in headers { match "^sender:.*advocacy" in headers action "obsd-advocacy" match "^sender:.*alpha" in headers action "obsd-alpha" match "^sender:.*arm" in headers action "obsd-arm" match "^sender:.*bugs" in headers action "obsd-bugs" match "^sender:.*hppa" in headers action "obsd-hppa" match "^sender:.*ipv6" in headers action "obsd-ipv6" match "^sender:.*mac68k" in headers action "obsd-mac68k" match "^sender:.*misc" in headers action "obsd-misc" match "^sender:.*ports-changes" in headers action "obsd-ports-changes" # first! match "^sender:.*ports" in headers action "obsd-ports" match "^sender:.*ppc" in headers action "obsd-ppc" match "^sender:.*source-changes" in headers action "obsd-source-changes" match "^sender:.*sparc" in headers action "obsd-sparc" match "^sender:.*tech" in headers action "obsd-tech" match "^sender:.*vax" in headers action "obsd-vax" match "^sender:.*www" in headers action "obsd-www" match "^sender:.*x11" in headers action "obsd-x11" } match "^from: secunia security advisories " in headers or "^list-id: " in headers { match "^subject:.*\\[full-disclosure\\]" in headers action "strip-fd" continue match "^subject: \\[ glsa" in headers action "drop" match "^subject: \\[usn-" in headers action "drop" match "^subject: \\[ mdksa" in headers action "drop" match "^subject: \\[security\\] \\[dsa" in headers action "drop" match "^from:.*announce-noreply@rpath\\.com" in headers action "drop" match "^list-post: " in headers action "sec-fd" match "^list-id: " in headers action "sec-bugtraq" } match "^list-id: wmii community " in headers action "fitug" match "^list-id: edri-news\\.mailman\\.edri\\.org" in headers action "edri" match "^To: CRYPTO-GRAM-LIST@LISTSERV\\.MODWEST\\.COM" in headers action "cryptogram" match "^From: L-Service@bsz-bw\\.de" in headers action "bibliothek" match "^from:.*rechnung\\.arcor\\.de" in headers action "arcor" match "^from:.*friend1@googlemail\\.com" in headers or "^from:.*friend2@yahoo\\.de" in headers # ... action "friends" match "^sender: tendra-dev-bounces@lists\\.tendra\\.org" in headers action "tendra-dev" match all action "inbox" fdm-1.7+cvs20140912/examples/w-maier.conf0000600000175000017500000001601010551223517016224 0ustar hawkhawk# vim: set nospell: # Settings. set lock-types flock set maximum-size 10M # Macros. $path = "%h/.maildir" $hep = "${path}/HEP" $lug = "${path}/LUG" $foss = "${path}/FOSS" $obsd = "${path}/OBSD" $bsd = "${path}/BSD" $linux = "${path}/Linux" $ubuntu = "${path}/Ubuntu" $sec = "${path}/SEC" # Actions. action "drop" drop # General. action "inbox" maildir "${path}/Inbox" action "junk" maildir "${path}/Junk" action "sent" maildir "${path}/Sent" action "spam" maildir "${path}/Spam" # LUGs. action "madlug" maildir "${lug}/Madlug" action "mkelug" maildir "${lug}/Mkelug" # UW TP. action "tp" maildir "${path}/TP" # Security. action "bugtraq" maildir "${sec}/BugTraq" action "dailydave" maildir "${sec}/DailyDave" action "secunia" maildir "${sec}/Secunia" # Debian. action "deb-ann" maildir "${linux}/Debian/Announce" action "deb-news" maildir "${linux}/Debian/News" action "deb-sec" maildir "${linux}/Debian/Security" # FreeBSD. action "fbsd" maildir "${bsd}/Free/Announce" action "fbsd-sec" maildir "${bsd}/Free/Security" # OpenBSD. action "obsd" maildir "${obsd}/General" action "obsd-bugs" maildir "${obsd}/Bugs" action "obsd-cvs" maildir "${obsd}/CVS" action "obsd-misc" maildir "${obsd}/Misc" action "obsd-ports" maildir "${obsd}/Ports" action "obsd-tech" maildir "${obsd}/Tech" # NetBSD. action "netbsd-sec" maildir "${bsd}/Net/Security" # Ubuntu. action "ubuntu-sec" maildir "${ubuntu}/Security" action "ubuntu" maildir "${ubuntu}/News" # FOSS. action "afs" maildir "${foss}/OpenAFS" action "elinks" maildir "${foss}/Elinks" action "hg" maildir "${foss}/Hg" action "ion" maildir "${foss}/Ion" action "ipython" maildir "${foss}/IPython" action "maildrop" maildir "${foss}/Maildrop" action "mutt" maildir "${foss}/Mutt" action "postfix" maildir "${foss}/Postfix" action "roundup" maildir "${foss}/Roundup" action "screen" maildir "${foss}/Screen" action "vim" maildir "${foss}/Vim" action "zsh" maildir "${foss}/Zsh" # Personal. action "family" maildir "${path}/Family" action "friend" maildir "${path}/Friend" # Work. action "absent" maildir "${hep}/Absent" action "cms" maildir "${hep}/CMS" action "dept" maildir "${hep}/Department" action "hep" maildir "${hep}/Inbox" action "root" maildir "${hep}/Root" action "glow" maildir "${hep}/GLOW" action "glow-absent" maildir "${hep}/Absent" action "nagios" maildir "${hep}/Nagios" action "req" maildir "${hep}/RQ" # SPAM. action "bmf" rewrite "bmf -p" # The stdin account is disabled: it will be ignored unless # explicitly requested using the -a switch on the command line. account "stdin" disabled stdin # Accounts. account "fm" pop3s server "mail.messagingengine.com" port 995 user "" pass "" account "hep" imaps server "hep.wisc.edu" port 993 user "" pass "" account "uw" pop3s server "wiscmail.wisc.edu" port 995 user "" pass "" account "gmail" pop3s server "pop.googlemail.com" port 995 user "" pass "" # Filter rules. # SPAM trap. # STUB: add relaydb based on bmf(1) at some point. match all action "bmf" continue match "^X-Spam-Status: Yes" in headers action "spam" # LUGs. match "^List-Id:.*madlug\\.madisonlinux\\.org" in headers action "madlug" match "^List-Id:.*mlug-list\\.mail\\.milwaukeelug\\.org" in headers action "mkelug" # FOSS. match "^List-Id:.*openafs-info\\.openafs\\.org" in headers action "afs" match "^List-Id:.*elinks-users\\.linuxfromscratch\\.org" in headers action "elinks" match "^List-Id:.*mercurial\\.selenic\\.com" in headers action "hg" match "^List-Id:.*ion-general\\.lists\\.berlios\\.de" in headers action "ion" match "^List-Id:.*ipython-user\\.scipy\\.org" in headers action "ipython" match "^List-Id:.*courier-maildrop\\.lists\\.sourceforge\\.net" in headers action "maildrop" match "^Sender:.*owner-mutt-users@mutt\\.org" in headers action "mutt" match "^List-Post:.*postfix-users@postfix\\.org" in headers action "postfix" match "^List-Id:.*roundup-users\\.lists\\.sourceforge\\.net" in headers action "roundup" match "^Mailing-List:.*vim-help@vim\\.org" in headers action "vim" match "^Mailing-List:.*zsh.*@sunsite\\.dk" in headers action "zsh" # Debian. match "^List-Id:.*lists\\.debian\\.org" in headers { match "^List-Id:.*announce" in headers action "deb-ann" match "^List-Id:.*news" in headers action "deb-news" match "^List-Id:.*security" in headers action "deb-sec" } # FreeBSD. match "^List-Id:.*freebsd-.*\\.freebsd\\.org" in headers { match "^List-Id:.*security-notifications" in headers action "fbsd-sec" match "^List-Id:.*announce" in headers { # I already get security announcements... match "^Subject.*FreeBSD Security Advisory FreeBSD-SA-" action "drop" match all action "fbsd" } } # OpenBSD. match "^Sender:[ \t]*owner-([a-z-]*)@openbsd\\.org" in headers { match string "%1" to "gnats" action "obsd-bugs" match string "%1" to "ports-changes" action "obsd-cvs" match string "%1" to "source-changes" action "obsd-cvs" match all action "obsd-%1" } # Ubuntu. match "^List-Id:.*ubuntu-.*\\.lists\\.ubuntu\\.com" in headers { match "^List-Id:.*security" in headers action "ubuntu-sec" match all action "ubuntu" } # NetBSD. match "^Sender:.*@netbsd\\.org" in headers { match "^Sender:.*tech-security" in headers action "netbsd-sec" } # Techpartners. match "^(To|Cc):.*techpartners@lists\\.wisc\\.edu" in headers action "tp" # DailyDave. match "^List-Id:.*dailydave\\.lists\\.immunitysec\\.com" in headers action "dailydave" # BugTraq. match "^List-Id:.*bugtraq\\.list-id\\.securityfocus\\.com" in headers action "bugtraq" # Secunia advisories. match "^From:.*sec-adv@secunia\\.com" in headers action "secunia" # Family. match "^From:.*mom@mom\\.net" or "^From:.*dad@(dad|father)\\.com" or "^From:.*sister@yahoo\\.com" in headers action "family" # Friends. match "^From:.*tom@hotmail\\.com" or "^From:.*dick@yahoo\\.com" or "^From:.*harry\\.cameron@gmail\\.com" in headers action "friend" # GLOW. match "^List-Id:.*glow-tech\\.cs\\.wisc\\.edu" in headers { match "^Subject: \\[glow-tech\\] GLOW Absent Report$" in headers action "glow-absent" match all action "glow" } # CMS. match "^List-Owner:.*CMS.*@LISTSERV\\.FNAL\\.GOV" in headers action "cms" # Physics. match "^(To|Cc):.*@physics\\.wisc\\.edu" in headers { match "^(To|Cc):.*(root|operator|cron)@" in headers action "root" match all action "dept" } # HEP. match "^(From|To):.*(hep|cs)\\.wisc\\.edu" in headers { match "^Subject: (UW-HEP)? (Condor|dCache|CFengine) Absent Report" in headers action "absent" match "(From|To|Cc):.*(help|req)@(nod|ginseng)\.hep\.wisc\.edu" in headers action "req" # HEP root mail. match "^(To|Cc):.*(root|operator|cron)@" in headers action "root" match "^Subject: (UP|DOWN|RECOVERY|PROBLEM):" in headers action "nagios" match all action "hep" } # Default filter. match all action "inbox" fdm-1.7+cvs20140912/examples/g-lando.conf0000600000175000017500000000644610676140530016221 0ustar hawkhawk# define the database for duplicates, use the message-id header for it and keep the database for 1 month; fdm needs to be built with 'make DB=1' to use this $db = "~/.fdm.duplicate-db" $key = "%[message_id]" cache $db expire 1 month # a reasonable timeout for my pop3s server set timeout 60 # my maildirs à la pine, with action, month and two digits year tags action 'received' maildir "%h/.maildir/%t-%m-%[year2]" action 'banca' maildir "%h/.maildir/%t-%m-%[year2]" action 'crux' maildir "%h/.maildir/%t-%m-%[year2]" action 'tuxonice' maildir "%h/.maildir/%t-%m-%[year2]" action 'dwm' maildir "%h/.maildir/%t-%m-%[year2]" action 'vimperator' maildir "%h/.maildir/%t-%m-%[year2]" action 'filosofia' maildir "%h/.maildir/%t-%m-%[year2]" action 'sent' maildir "%h/.maildir/%t-%m-%[year2]" action 'osml' maildir "%h/.maildir/%t-%m-%[year2]" # a general maildir for cron jobs action 'cron' maildir "%h/.maildir/%t" # a sound for all my mails action 'sound' exec "/usr/bin/aplay -q ~/.xchat2/sounds/bong.au" # esmtp, my very simple MTA, is able to deliver local mail from cron jobs and so, but does not add Date: and From: headers action 'esmtpdate' add-header "From" "%u@%[hostname]" action 'esmtpfrom' add-header "Date" "%[rfc822date]" # the maildir for duplicates, since some false duplicates can be detected when a previous instance of fdm gets interrupted action 'duplicates' maildir "%h/.maildir/%t" # the standard drop and keep actions action 'null' drop action 'one' keep # my gmail account account 'gmail' pop3s server "pop.gmail.com" port 995 user "x" pass "y" # the standard input account account 'stdin' disabled stdin # first of all, the sound match all action "sound" continue # add the missing headers for local mail match account "stdin" action 'esmtpdate' continue match account "stdin" action 'esmtpfrom' continue # cron jobs match account "stdin" and "^(From).*(root|patroclo7|mvpozzato)" in headers and "^(Subject).*(cron)" in headers action "cron" # local mail does not need to be filtered any further match account "stdin" action "received" # catch duplicates match not string $key to "" { match in-cache $db key $key action "duplicates" } # update the duplicates db match all action to-cache $db key $key continue # drop match "^(From).*(postmaster\@postmaster\.libero\.it)" in headers action "null" # the mails I have sent from gmail web interface match "^(From).*(patroclo7\@gmail.com)" in headers action "sent" # philosophy mailing lists; stuff about jobs needs to be also in the default maildir match "^(To|List-Id).*(liverpool|swif|sequitur)" in headers { match "^(Subject).*(job)" in headers action "received" continue match all action "filosofia" } # bank stuff match "^(From).*(iwbank)" in headers action "banca" # stuff from OS mailing lists and forums match "^(To|List-Id).*(crux)" in headers action "crux" match "^(To|List-Id).*(vimperator)" in headers action "vimperator" match "^(To|List-Id).*(dwm).*(suckless)" in headers action "dwm" match "^(To|List-Id).*(suspend2|tuxonice)" in headers action "tuxonice" match "^(To|List-Id).*(archlinux|crealabs|fdm-users|fish-users|metalog-users|conkeror|ratpoison|muttprint|screen-users|suspend2|rxvt-unicode)" in headers or "^(Return-Path).*(crealabs|pharsc2|archlinux)" in headers action "osml" # the default destination match all action "received" fdm-1.7+cvs20140912/examples/n-marriott.conf0000600000175000017500000003654711670656003017003 0ustar hawkhawk#set delete-oversized #set default-user "nicholas" #set allow-multiple set purge-after 50 #set proxy "socks://localhost:1080/" set timeout 120 set parallel-accounts 2 # My fdm configuration base path. $base = "%h/.fdm.d" # Lock file path. set lock-file "${base}/lock" # Maximum mail size. set maximum-size 31 MB # Verify SSL certificates. set verify-certificates # These seem to vaguely work okay for me. set queue-high 5 set queue-low 3 # Use dotlock locking, since FreeBSD doesn't like anything else on NFS. #set lock-types dotlock # Macro holding the path where the maildirs are. $path = "%h/mail" # My main inbox and junk mail directories. action "inbox" maildir "${path}/_inbox" action "junk" maildir "${path}/_junk" # Account definitions. All passwords and most usernames are read from .netrc. account "ntlworld" pop3s server "pop.ntlworld.com" port 995 account "gmail.nicholas.marriott" imaps server "imap.googlemail.com" port 993 user "nicholas.marriott@gmail.com" account "gmail.nicm321" pop3s server "pop.googlemail.com" port 995 user "nicm321@gmail.com" account "gmail.nicm123" imaps server "imap.googlemail.com" port 993 user "nicm123@gmail.com" account "yahoo" pop3s server "pop.mail.yahoo.com" port 995 account "gmx" pop3 server "pop.gmx.net" # The stdin account is disabled: it will be ignored unless explicitly requested # using the -a switch on the command line. account "stdin" disabled stdin # RSS feed account for RATM, http://lfod.us/sw/ratm/. account "rss" disabled stdin # ------------------------------------------------------------------------------ # These two definitions are part of killfiling - the action appends the # message-id to my block list and the macro is used to check that no message-id # in the references or in-reply-to headers matches any in the file, if it does # the mail is dropped later. This means I not only don't need to read mail from # idiots, I also don't have to read many of the hundred follow-ups telling them # they are an idiot. Use with care: this can be slow. # ------------------------------------------------------------------------------ action "dead" maildir "${path}/diediedie" action "diediedie" { exec "echo '%[message_id]' >>${base}/ignore" action "dead" } $is_dead = "echo '%1'|grep -Fqif ${base}/ignore" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Archive mail to mailboxes. # ------------------------------------------------------------------------------ account "archive" disabled maildirs { "${path}/blug" "${path}/linux-rdma" "${path}/linux-rt-users" "${path}/openbsd-{misc,ports,www,bugs,source-changes,ports-changes}" "${path}/openssh-unix-dev" "${path}/rss" } #action "archive" mbox "${path}/%[maildir]-archives/%[maildir]-%yq%Q" compress action "archive" drop match account "archive" { # Archive mail with invalid dates or older than 30 days. match age invalid or age > 30 days action "archive" # Don't let any other mail get to the normal rules. match all action keep } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # RSS. # ------------------------------------------------------------------------------ match account "rss" { $crap = "football|sport|prince harry|horse|jockey|royal family|" + "britney spears|cricket|tennis|snooker|motorsport|rugby|" + "princess diana|heather mills|paul mccartney|jamie oliver|" + "music award|candle-lit|in memory|concert|celtic|the queen|" + "richard and judy|madeleine mccann|motoring costs|john crace|" + "richard branson|commonwealth games|olympic games|jill dando|" + "vigil|day of mourning|war dead|amy winehouse|prince charles|" + "remembrance day" match "(${crap})" action drop # Never worth reading... match "^From:.*overheard in new york" in headers { match "^Subject:.*Wednesday.*" in headers action drop match "^Subject:.*One-Liner.*" in headers action drop } match "From:.*techdirt" in headers { match "Subject:.*DailyDirt:.*" in headers action drop match "Subject:.*Of The Week.*" in headers action drop } match "^From:.*tom's hardware" in headers { match "^Subject:.*Presented By:$" in headers action drop } match "^From:.*wired" in headers { match "^Subject:.*Google-a-Day Puzzle.*" in headers action drop } match all action maildir "${path}/rss" } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Newsgroups. These are disabled so I can run them using a "fdm -anews f" from # a seperate cronjob less often than normal mail accounts. # ------------------------------------------------------------------------------ account "news" disabled nntp server "infosun2.rus.uni-stuttgart.de" groups { "comp.unix.bsd.openbsd.misc" # "comp.unix.programmer" } cache "${base}/news" match account "news" { match "\\" in body action "junk" # comp.unix.programmer pointless argument usual suspects. match "^From:.*rweikusat@mssgmbh.com" in headers action drop match "^From:.*davids@webmaster.com" in headers action drop # Idiots. match "^From:.*MI5-?Victim@mi5.gov.uk" in headers action drop match "^Organization:.*www.altopia.com" in headers action drop match "^From:.*robin_carey5@yahoo.co.uk" in headers action drop # Spammers. match "^From:.*jak@isp2dial.com" in headers action drop match "^From:.*doctor@doctor.nl2k.ab.ca" in headers action drop # Match based on the group name. match all action maildir "${path}/news-%[group]" } # ------------------------------------------------------------------------------ # Catch local mail early. match account "stdin" action "inbox" # Catch duplicate messages using a message-id cache. $db = "${base}/duplicates" $key = "%[message_id]" cache $db expire 1 week match not string $key to "" { # Filter messages already in the cache. match in-cache $db key $key { # Stupid Google Groups sends me everything twice. match "List-Id:.*belfastlinux.googlegroups.com" in headers action drop match all action maildir "${path}/_duplicates" } # Add message-ids to the cache. match all action to-cache $db key $key continue } # Junk (eejits). match ".*YAHOO.*BOOTER.*" in body action drop # Junk (spam). match "^From:.*ezine@recruitni.com" in headers action drop match "^From:.*@*.chase.com" in headers action drop match "^From:.*@*.chaseonline.com" in headers action drop match "^From:.*@citi-bank.com" in headers action drop match "^From:.*@emaillabs.com" in headers action drop match "^From:.*baypos@gmail.com" in headers action drop match "^From:.*E-Greeting" in headers action drop match "^From:.*@postcard.org" in headers action drop match "^From:.*@mail.itp.net" in headers action drop match "^From:.*@faith-h.net" in headers action drop match "^From:.*reponse@altech-france.fr" in headers action drop match "^From:.*ecards@americangreetings.com" in headers action drop # Junk (ISP/website garbage). match "^From:.*@newsletter.ntlworld.com" in headers action drop match "^From:.*@newsletter.virginmedia.com" in headers action drop match "^From:.*mailings@(gmxnet.de|gmx.net|gmx-gmbh.de)" in headers action drop match "^From:.*@friendsreunited.co.uk" in headers action drop match "^From:.*offers@dabs.com" in headers action drop # AWAD uninteresting crap. match "^Subject:[ \t]*AWADmail Issue [0-9]+" in headers action drop # Copy invalid dates to a special action so I can inspect them. match age invalid action maildir "${path}/_invalid" continue # Copy HTML mail to special action to test attachment parsing. #match attachment any-type "*/html" action maildir "${path}/_html" continue # Test rewriting through cat. #match all action rewrite "cat" continue # Test mbox delivery. #match size > 1K and size < 4K action mbox "${path}/_mbox" compress continue # Add line count header. match all action add-header "Lines" value "%[lines]" continue # Add rule number header. #match all action add-header "Rule" value "%[rule]" continue # BLUG mailing list. match "^X-Apparently-To:.*belfastlinux@googlegroups.com" in headers or "List-ID:.*belfastlinux.googlegroups.com" in headers action maildir "${path}/blug" # ------------------------------------------------------------------------------ # OpenBSD. # ------------------------------------------------------------------------------ match "^Sender:.*owner-([a-z-]*)@openbsd.org" in headers { # Tag the mail. match all action tag "list" value "%1" continue # Drop any mail which is replying to a message-id on the ignore list. match "^References:[ \t]*(.*)" in headers and exec ${is_dead} returns (0, ) action "dead" match "^In-Reply-To:[ \t]*([^()]*)" in headers and exec ${is_dead} returns (0, ) action "dead" # Drop a selection of eejits/trolls/time wasters/whiners/pompous # twits/uninteresting persons and add to ignore list. match "^From:.*john@johntate.org" in headers action "diediedie" match "^From:.*billitch@gmail.com" in headers action "diediedie" match "^From:.*rms@1407.org" in headers action "diediedie" match "^From:.*clock@twibright.com" in headers action "diediedie" match "^From:.*dfeustel@mindspring.com" in headers action "diediedie" match "^From:.*peter_philipp@freenet.de" in headers action "diediedie" match "^From:.*philipp.peter@freenet.de" in headers action "diediedie" match "^From:.*suck@my-balls.com" in headers action "diediedie" match "^From:.*chefren@pi.net" in headers action "diediedie" match "^From:.*demuel@thephinix.org" in headers action "diediedie" match "^From:.*lvl@omnitec.net" in headers action "diediedie" match "^From:.*timo.schoeler@riscworks.net" in headers action "diediedie" match "^From:.*ropers@gmail.com" in headers action "diediedie" match "^From:.*leonleon77@gmail.com" in headers action "diediedie" match "^From:.*af.gourmet@videotron.ca" in headers action "diediedie" match "^From:.*nicedaemon@googlemail.com" in headers action "diediedie" match "^From:.*vim.unix@googlemail.com" in headers action "diediedie" match "^From:.*emaillistemail@gmail.com" in headers action "diediedie" match "^From:.*4625khz@gmail.com" in headers action "diediedie" match "^From:.*Rhubbell@iHubbell.com" in headers action "diediedie" match "^From:.*donaldcallen@gmail.com" in headers action "diediedie" match "^From:.*nealhogan@gmail.com" in headers action "diediedie" match "^From:.*glisten@witworx.com" in headers action "diediedie" match "^From:.*ma1l1ists@yahoo.co.uk" in headers action "diediedie" match "^From:.*yclwebmaster@gmail.com" in headers action "diediedie" match "^From:.*fritz@.*.rodent.frell.theremailer.net" in headers action "diediedie" # Special-case announce. match string "%[list]" to "announce" action "inbox" # Special-case gnats. match string "%[list]" to "gnats" action "${path}/openbsd-bugs" # Deliver to openbsd-%1. match all action maildir "${path}/openbsd-%[list]" } # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # NetBSD. # ------------------------------------------------------------------------------ #match "^List-Id: ([a-z-]*).NetBSD.org" in headers # action maildir "${path}/netbsd-%1" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # FreeBSD. # ------------------------------------------------------------------------------ #match "^Sender:.*owner-([a-z-]*)@freebsd.org" in headers # action maildir "${path}/freebsd-%1" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # GNU Screen. # ------------------------------------------------------------------------------ match "^X-BeenThere:.*screen-users@gnu.org" in headers action maildir "${path}/screen-users" match "^X-BeenThere:.*screen-devel@gnu.org" in headers action maildir "${path}/screen-devel" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # GNU ncurses. # ------------------------------------------------------------------------------ match "^X-BeenThere:.*bug-ncurses@gnu.org" in headers action maildir "${path}/bug-ncurses" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # libevent # ------------------------------------------------------------------------------ match "^Sender: owner-libevent-users@freehaven.net" in headers action maildir "${path}/libevent-users" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # btpd # ------------------------------------------------------------------------------ match "^Sender: btpd-users@googlegroups.com" in headers action maildir "${path}/btpd-users" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Linux # ------------------------------------------------------------------------------ match "^X-Mailing-List:.*linux-rdma@vger.kernel.org" in headers action maildir "${path}/linux-rdma" match "^X-Mailing-List:.*linux-rt-users@vger.kernel.org" in headers action maildir "${path}/linux-rt-users" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # OpenSSH. # ------------------------------------------------------------------------------ match "^X-BeenThere:.*openssh-unix-dev@mindrot.org" in headers action maildir "${path}/openssh-unix-dev" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # OSS Security. # ------------------------------------------------------------------------------ match "^List-Post:.*oss-security@lists.openwall.com" in headers action maildir "${path}/oss-security" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # OSS Security. # ------------------------------------------------------------------------------ match "^X-Mailing-List:.*austin-group-l" in headers action maildir "${path}/austin-group-l" # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Poem-A-Day # ------------------------------------------------------------------------------ match "^To:.*poetnews@poets.org" in headers action maildir "${path}/poems" # ------------------------------------------------------------------------------ # OpenMAMA # ------------------------------------------------------------------------------ match "^List-Id:.*openmama-dev.lists.openmama.org" in headers action maildir "${path}/openmama-dev" match "^List-Id:.*openmama-users.lists.openmama.org" in headers action maildir "${path}/openmama-users" # Default action. match all action "inbox" fdm-1.7+cvs20140912/examples/f-terbeck.conf0000600000175000017500000004511410676140530016535 0ustar hawkhawk### fdm configuration ### vim:ft=config ### ### All in one replacement for {get,fetch}mail and procmail. ### ### ### Frank Terbeck ### ### Last-Modified: Thu Mar 8 17:03:05 2007 ### ########################################################################### ### The following strings are replaced in pipe commands and ### maildir/mbox paths: ### %a: account name ### %h: user's home directory ### %n: user's uid ### %t: action name if performing action ### %u: name of user ### %H: current hour (00-23) ### %M: current minute (00-59) ### %S: current second (00-59) ### %d: current day of the month (00-31) ### %m: current month (01-12) ### %y: current year ### %W: current day of the week (0-6, Sunday is 0) ### %Y: current day of the year (000-365) ### %Q: current quarter (1-4) ########################################################################### ### Some macros used below. ### where I keep my mail $path = "%h/Mail" ### where I keep my config files $cfgdir = "%h/etc/fdm" ### location of killfile $killfile = "%h/etc/mailnews/killfile" ### spam filter commands $com_spam_filter = "spamprobe receive" $com_train_good = "spamprobe good" $com_train_spam = "spamprobe spam" ### some filters use these unix commands: $com_sed = "sed" $com_awk = "awk" $com_perl = "perl" ### spam header expressions $header_spam = "X-SpamProbe" $filtered_spam = "SPAM" $filtered_good = "GOOD" $filter_trained = " TRAINED" ### if do_catchall is "yes", all incoming mail will be saved ### to ${catchall_mbox}; useful for testing (so nothing gets lost). $do_catchall = "nope" $catchall_mbox = "%h/catchall.mbox" ### this is used by my archive action. ### Mails older than this (in days) are moved to the archive. %max_age = 30 ### zblog $com_zblog = "%h/bin/zblog" ### print from headers (Sender, From, From:, Reply-To:) ### used for killfiling. $com_pfh = "${com_awk} '/^(From|From:|Reply-To:|Sender)/{sub(/[^ \\t]*[ \\t]*/,\"\",$0);print};/^\$/{exit 0}'" ### list of headers I don't want. $ill_headers = "(" $ill_headers = "${ill_headers}X-Qmail-Scanner.*|X-SA-Exim.*|X-CR-.*|" $ill_headers = "${ill_headers}X-MIME-.*|X-Authentication-.*|" $ill_headers = "${ill_headers}X-Mailman-.*|X-Spam-.*|X-MimeOLE|" $ill_headers = "${ill_headers}X-Msmail-.*|X-Priority|X-MS-.*|" $ill_headers = "${ill_headers}Thread-Topic|Thread-Index|" $ill_headers = "${ill_headers}X-OriginalArrivalTime|" $ill_headers = "${ill_headers}X-Face|DKIM-Signature|" $ill_headers = "${ill_headers}X-IronPort-.*|DomainKey-Signature|" $ill_headers = "${ill_headers}X-Rc-.*" $ill_headers = "${ill_headers})" ### perl script that filters out unwanted headers $com_filter_headers = "${com_perl} -e '\$k=\$e=0;while(<>){if(\$e||m/^$/){\$e=1;print;next;}" $com_filter_headers = "${com_filter_headers}if(m/^[ \\t]+/&&\$k){next;}if(m/^${ill_headers}:/i)" $com_filter_headers = "${com_filter_headers}{\$k=1;next;}if(\$k){\$k=0;}print;}'" ########################################################################### ### This is used to set the maximum size of a mail. Mails larger than ### this limit are dropped and, if applicable, not deleted from the ### server. set maximum-size 128M ### If this option is specified, fdm(1) attempts to delete messages ### which exceed maximum-size, and continue.If it is not specified, ### oversize messages are a fatal error and cause fdm(1) to abort. #set delete-oversized ### If this option is specified, fdm(1) does not attempt to create a ### lock file and allows multiple instances to run simultaneously. #set allow-multiple ### This sets an alternative lock file. The default is ~/.fdm.lock ### for non-root users and /var/db/fdm.lock for root. set lock-file "${cfgdir}/fdm.lock" ### This specifies the locks to be used for mbox locking. Possible ### types are fcntl, flock, and dotlock. The flock and fcntl types ### are mutually exclusive. The default is flock. #set lock-types flock ### This sets the default user to change to before delivering mail, ### if fdmis running as root and no alternative user is specified as ### part of the action or rule. #set default-user "hawk" ### This specifies the domains to be used when looking for users with ### the from-headers keyword. The default is the computer's hostname. #set domain "bewatermyfriend.org" ### This allows the headers to be examined when looking for users to ### be set. The default is to look only at the "From" and "Cc" ### headers. The headers are case-insensitive. #set header ### This instructs fdm to proxy all connections through url. HTTP ### and SOCKS5 proxies are supported at present (URLs of the form ### http://host[:port] or socks://[user:pass@]host[:port]). ### No authentication is supported for HTTP. #set proxy ### This option controls what fdm does with mail that reaches the ### end of the ruleset (mail that matches no rules or matches only ### rules with the continue keyword). drop will cause such mail to ### be discarded, and keep will attempt to leave the mail on the ### server. The default is to keep the mail and log a warning that ### it reached the end of the ruleset. set unmatched-mail keep ### This option makes fdm attempt to purge deleted mail from the ### server (if supported) after count mails have been retrieved. set purge-after none ### If set, fdm will not insert a 'Received' header into each mail. #set no-received ### This specifies the umask(2) to use when creating files. 'user' ### means to use the umask set when fdm is started, or umask may be ### specified as a three-digit octal number. set file-umask 077 ### This option allows the default group ownership of files and ### directories created by fdm(1) to be specified. 'group' may be a ### group name string or a numeric gid. 'user' does nothing. set file-group user ### This controls the maximum time to wait for a server to send data ### before closing a connection. The default is 900 seconds. set timeout 900 ########################################################################### ### include account information from seperate file. ### it contains lines that look like this: ### account "name" server pop3 "pop3.serv.tld" user "uname" pass "pwd" include "${cfgdir}/accounts.conf" ########################################################################### ### simple actions action "drop" drop action "keep" keep ### zblog action "zblog" pipe "${com_zblog} email" action "zcomm" pipe "${com_zblog} commentmode" ### killfiling action "killfile" maildir "${path}/Trash" ### a mailbox to rule them all action "catchall" mbox "${catchall_mbox}" ### spam actions action "spam" maildir "${path}/Spam" action "train-spam" pipe "${com_train_spam}" action "train-good" pipe "${com_train_good}" action "sp-add-header" add-header "${header_spam}" "%[command1]" action "sp-remove-header" remove-header "${header_spam}" action "sp-add-trained-good" add-header "${header_spam}" "${filtered_good}${filter_trained}" action "sp-add-trained-spam" add-header "${header_spam}" "${filtered_spam}${filter_trained}" ### action for adding Lines: headers ### Current CVS versions can do this themselves. action "add-lines-header" add-header "Lines" "%[body_lines]" ### action to remove unneeded headers action "remove-ill-headers" rewrite "${com_filter_headers}" ### mark as read in maildirs. this requires /bin/sh to be somewhat POSIXly ### correct. ash, ksh, bash etc. will do; older bourne shells (like /bin/sh ### on OpenSolaris) will not. You'll need to use {base,dir}name with these. action "maildir-mark-as-read" exec "mf=\"%[mail_file]\" ; mv \"\${mf}\" \"\${mf%%/*}\"/../cur/\"\${mf##*/}:2,S\"" ### archiving action action "archive" mbox "${path}/archive/%[maildir]-%yq%Q" compress ### handle mailinglists ### path actions #{{{ action "inbox" maildir "${path}/Inbox" action "debian-announce" maildir "${path}/debian-announce" action "debian-boot" maildir "${path}/debian-boot" action "debian-curiosa" maildir "${path}/debian-curiosa" action "debian-devel" maildir "${path}/debian-devel" action "debian-devel-announce" maildir "${path}/debian-devel-announce" action "debian-mentors" maildir "${path}/debian-mentors" action "debian-news-german" maildir "${path}/debian-news-german" action "debian-outbox" maildir "${path}/debian-outbox" action "debian-policy" maildir "${path}/debian-policy" action "debian-project" maildir "${path}/debian-project" action "debian-pts" maildir "${path}/debian-pts" action "debian-publicity" maildir "${path}/debian-publicity" action "debian-release" maildir "${path}/debian-release" action "debian-security" maildir "${path}/debian-security" action "debian-vote" maildir "${path}/debian-vote" action "debian-women" maildir "${path}/debian-women" action "openbsd-announce" maildir "${path}/openbsd-announce" action "openbsd-ipv6" maildir "${path}/openbsd-ipv6" action "openbsd-misc" maildir "${path}/openbsd-misc" action "openbsd-ports" maildir "${path}/openbsd-ports" action "openbsd-ports-bugs" maildir "${path}/openbsd-ports-bugs" action "openbsd-ports-security" maildir "${path}/openbsd-ports-security" action "openbsd-security-announce" maildir "${path}/openbsd-security-announce" action "openbsd-tech" maildir "${path}/openbsd-tech" action "openbsd-www" maildir "${path}/openbsd-www" action "openbsd-x11" maildir "${path}/openbsd-x11" action "mutt-dev" maildir "${path}/mutt-dev" action "mutt-users" maildir "${path}/mutt-users" action "fvwm" maildir "${path}/fvwm" action "fvwm-workers" maildir "${path}/fvwm-workers" action "grml" maildir "${path}/grml" action "grml-devel" maildir "${path}/grml-devel" action "zsh-users" maildir "${path}/zsh-users" action "zsh-workers" maildir "${path}/zsh-workers" action "cmus-devel" maildir "${path}/cmus-devel" action "leafnode" maildir "${path}/leafnode" action "remind-fans" maildir "${path}/remind-fans" action "screen-users" maildir "${path}/screen-users" action "slrn-users" maildir "${path}/slrn-users" action "tuhs" maildir "${path}/tuhs" action "vim-users" maildir "${path}/vim-users" action "linux-kernel" maildir "${path}/linux-kernel" action "kernelnewbies" maildir "${path}/linux-kernel-newbies" action "linux-elitists" maildir "${path}/linux-elitists" action "bugtraq" maildir "${path}/bugtraq" action "full-disclosure" maildir "${path}/full-disclosure" action "pen-test" maildir "${path}/pen-test" action "schopppe" maildir "${path}/schopppe" #}}} ### rewrite actions (some mailinglist add tags to their subjects...) ### '1,/^$/...' only rewrites lines in the mail _headers_. #{{{ action "strip-full-disclosure" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[Full-disclosure\\] /\\1/'" action "strip-remind-fans" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[Remind-Fans\\] /\\1/'" action "strip-fvwm" rewrite "${com_sed} '1,/^$/s/^\\(Subject:\\) FVWM:/\\1/'" action "strip-grml" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[Grml\\] /\\1/'" action "strip-leafnode" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[leafnode-list\\] /\\1/'" action "strip-linux-elitists" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[linux-elitists\\] /\\1/'" action "strip-tuhs" rewrite "${com_sed} '1,/^$/s/^\\(Subject:.*\\)\\[TUHS\\] /\\1/'" #}}} ########################################################################### ### accounts for spam-training ### -asp-train-spam ### trains the message from stdin as spam; ### moves the mail to the appropriate folder ### -asp-train-good ### trains the message from stdin as ham; ### continues with normal rules to sort the message ### to the folder it belongs to. account "sp-train-spam" disabled stdin account "sp-train-good" disabled stdin ### sometimes you want to test new things by providing mails on stdin account "stdin" disabled stdin ########################################################################### ### Automatic archiving. ### This is a nice idea, I shamelessly stole from NicM's config. ### Mail is kept in Maildirs for 30 days. After that it is ### automatically moved to compressed mboxes, which in turn ### may by periodically removed (by cron for examples, or ### by hand). This (again) uses an account that is disabled, ### so it can be explicitly called by '-aarchive'. account "archive" disabled maildirs { #{{{ "${path}/Inbox" "${path}/bugtraq" "${path}/cmus-devel" "${path}/debian-*" "${path}/full-disclosure" "${path}/fvwm*" "${path}/grml*" "${path}/leafnode" "${path}/linux-*" "${path}/mutt-*" "${path}/openbsd-*" "${path}/pen-test" "${path}/remind-fans" "${path}/schopppe" "${path}/screen-users" "${path}/slrn-users" "${path}/tuhs" "${path}/vim-users" "${path}/zsh-*" #}}} } match account "archive" and age > %{max_age} days action "archive" match account "archive" action "keep" ########################################################################### ### handle mail ### do catchall as early as possible. match string "${do_catchall}" to "^yes$" action "catchall" continue ########## zblog messages ########## ### mails to blog -at- bewatermyfriend -dot- org are meant for zblog. match account "blog" action "zblog" match account "comments" action "zcomm" ########## killfiling ########## ### killfiling on From, From:, Reply-To: and Sender headers ### If your killfile is rather large, this will slow down fdm ### considerably. So do this only on people that really disturb you. match pipe "${com_pfh} | grep -Eqif ${killfile}" returns ( 0, ) actions { "killfile" "maildir-mark-as-read" } ########## spam handling ########## ### force message from stdin to spamfolder and train it as spam match account "sp-train-spam" { match all action "train-spam" continue match "^${header_spam}" in headers action "sp-remove-header" continue match all action "sp-add-trained-spam" continue match all action "spam" } ### train message from stdin as ham match account "sp-train-good" action "train-good" continue match account "sp-train-good" and "^${header_spam}" in headers action "sp-remove-header" continue match account "sp-train-good" action "sp-add-trained-good" continue ### scan for spam match not "^${header_spam}" in headers { match pipe "${com_spam_filter}" returns ( , "(.*)" ) { match "^${header_spam}" in headers action "sp-remove-header" continue match all action "sp-add-header" continue } } ### move messages marked as spam to spamfolder match "^${header_spam}:[ \t]*${filtered_spam}" in headers action "spam" ########## modifying headers ########## ### add Lines: header if it is missing. match not "^Lines:" in headers action "add-lines-header" continue ### remove headers I don't care about match "^${ill_headers}:" in headers action "remove-ill-headers" continue ########## sorting ########## ### debian- mailinglists and pts match "^List-Id:[ \t]* * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "fdm.h" #include "match.h" int match_unmatched_match(struct mail_ctx *, struct expritem *); void match_unmatched_desc(struct expritem *, char *, size_t); struct match match_unmatched = { "unmatched", match_unmatched_match, match_unmatched_desc }; int match_unmatched_match(struct mail_ctx *mctx, unused struct expritem *ei) { if (mctx->matched) return (MATCH_FALSE); return (MATCH_TRUE); } void match_unmatched_desc(unused struct expritem *ei, char *buf, size_t len) { strlcpy(buf, "unmatched", len); } fdm-1.7+cvs20140912/deliver-write.c0000600000175000017500000000516411030761274015134 0ustar hawkhawk/* $Id: deliver-write.c,v 1.35 2008/06/26 18:41:00 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "fdm.h" #include "deliver.h" int deliver_write_deliver(struct deliver_ctx *, struct actitem *); void deliver_write_desc(struct actitem *, char *, size_t); struct deliver deliver_write = { "write", DELIVER_ASUSER, deliver_write_deliver, deliver_write_desc }; int deliver_write_deliver(struct deliver_ctx *dctx, struct actitem *ti) { struct account *a = dctx->account; struct mail *m = dctx->mail; struct deliver_write_data *data = ti->data; char *path; FILE *f; path = replacepath(&data->path, m->tags, m, &m->rml, dctx->udata->home); if (path == NULL || *path == '\0') { if (path != NULL) xfree(path); log_warnx("%s: empty command", a->name); return (DELIVER_FAILURE); } if (data->append) { log_debug2("%s: appending to %s", a->name, path); f = fopen(path, "a"); } else { log_debug2("%s: writing to %s", a->name, path); f = fopen(path, "w"); } if (f == NULL) { log_warn("%s: %s: fopen", a->name, path); goto error; } if (fwrite(m->data, m->size, 1, f) != 1) { log_warn("%s: %s: fwrite", a->name, path); goto error; } if (fflush(f) != 0) { log_warn("%s: %s: fflush", a->name, path); goto error; } if (fsync(fileno(f)) != 0) { log_warn("%s: %s: fsync", a->name, path); goto error; } fclose(f); xfree(path); return (DELIVER_SUCCESS); error: xfree(path); return (DELIVER_FAILURE); } void deliver_write_desc(struct actitem *ti, char *buf, size_t len) { struct deliver_write_data *data = ti->data; if (data->append) xsnprintf(buf, len, "append \"%s\"", data->path.str); else xsnprintf(buf, len, "write \"%s\"", data->path.str); } fdm-1.7+cvs20140912/fetch-maildir.c0000600000175000017500000002352711634126723015071 0ustar hawkhawk/* $Id: fetch-maildir.c,v 1.93 2011/09/14 13:36:19 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "fdm.h" #include "fetch.h" int fetch_maildir_commit(struct account *, struct mail *); void fetch_maildir_abort(struct account *); u_int fetch_maildir_total(struct account *); void fetch_maildir_desc(struct account *, char *, size_t); void fetch_maildir_free(void *); int fetch_maildir_makepaths(struct account *); void fetch_maildir_freepaths(struct account *); int fetch_maildir_poll(struct account *); int fetch_maildir_state_init(struct account *, struct fetch_ctx *); int fetch_maildir_state_build(struct account *, struct fetch_ctx *); int fetch_maildir_state_next(struct account *, struct fetch_ctx *); int fetch_maildir_state_open(struct account *, struct fetch_ctx *); int fetch_maildir_state_mail(struct account *, struct fetch_ctx *); struct fetch fetch_maildir = { "maildir", fetch_maildir_state_init, NULL, fetch_maildir_commit, fetch_maildir_abort, fetch_maildir_total, fetch_maildir_desc }; void fetch_maildir_free(void *ptr) { struct fetch_maildir_mail *aux = ptr; xfree(aux); } /* Make an array of all the paths to visit. */ int fetch_maildir_makepaths(struct account *a) { struct fetch_maildir_data *data = a->data; char *path; u_int i, j; glob_t g; struct stat sb; data->paths = xmalloc(sizeof *data->paths); ARRAY_INIT(data->paths); for (i = 0; i < ARRAY_LENGTH(data->maildirs); i++) { path = ARRAY_ITEM(data->maildirs, i); if (glob(path, GLOB_BRACE|GLOB_NOCHECK, NULL, &g) != 0) { log_warn("%s: glob(\"%s\")", a->name, path); goto error; } if (g.gl_pathc < 1) fatalx("glob returned garbage"); for (j = 0; j < (u_int) g.gl_pathc; j++) { xasprintf(&path, "%s/cur", g.gl_pathv[j]); ARRAY_ADD(data->paths, path); if (stat(path, &sb) != 0) { log_warn("%s: %s", a->name, path); ARRAY_TRUNC(data->paths, 1); continue; } if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; log_warn("%s: %s", a->name, path); ARRAY_TRUNC(data->paths, 1); continue; } xasprintf(&path, "%s/new", g.gl_pathv[j]); ARRAY_ADD(data->paths, path); if (stat(path, &sb) != 0) { log_warn("%s", path); ARRAY_TRUNC(data->paths, 2); continue; } if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; log_warn("%s", path); ARRAY_TRUNC(data->paths, 2); continue; } } globfree(&g); } return (0); error: fetch_maildir_freepaths(a); return (-1); } /* Free the array. */ void fetch_maildir_freepaths(struct account *a) { struct fetch_maildir_data *data = a->data; u_int i; if (data->paths == NULL) return; for (i = 0; i < ARRAY_LENGTH(data->paths); i++) xfree(ARRAY_ITEM(data->paths, i)); ARRAY_FREEALL(data->paths); data->paths = NULL; } /* Count maildir total. */ int fetch_maildir_poll(struct account *a) { struct fetch_maildir_data *data = a->data; u_int i; char *path, entry[MAXPATHLEN]; DIR *dirp; struct dirent *dp; struct stat sb; data->total = 0; for (i = 0; i < ARRAY_LENGTH(data->paths); i++) { path = ARRAY_ITEM(data->paths, i); log_debug("%s: trying path: %s", a->name, path); if ((dirp = opendir(path)) == NULL) { log_warn("%s: %s: opendir", a->name, path); return (-1); } while ((dp = readdir(dirp)) != NULL) { if (dp->d_type == DT_REG) { data->total++; continue; } if (dp->d_type != DT_UNKNOWN) continue; if (ppath(entry, sizeof entry, "%s/%s", path, dp->d_name) != 0) { log_warn("%s: %s: printpath", a->name, path); closedir(dirp); return (-1); } if (stat(entry, &sb) != 0) { log_warn("%s: %s: stat", a->name, entry); closedir(dirp); return (-1); } if (!S_ISREG(sb.st_mode)) continue; data->total++; } if (closedir(dirp) != 0) { log_warn("%s: %s: closedir", a->name, path); return (-1); } } return (0); } /* Commit mail. */ int fetch_maildir_commit(struct account *a, struct mail *m) { struct fetch_maildir_data *data = a->data; struct fetch_maildir_mail *aux; aux = m->auxdata; if (m->decision == DECISION_DROP) { /* Add mail to the unlink list. */ ARRAY_ADD(&data->unlinklist, xstrdup(aux->path)); } return (FETCH_AGAIN); } /* Abort fetch. */ void fetch_maildir_abort(struct account *a) { struct fetch_maildir_data *data = a->data; u_int i; for (i = 0; i < ARRAY_LENGTH(&data->unlinklist); i++) xfree(ARRAY_ITEM(&data->unlinklist, i)); ARRAY_FREE(&data->unlinklist); if (data->dirp != NULL) closedir(data->dirp); fetch_maildir_freepaths(a); } /* Return total mails. */ u_int fetch_maildir_total(struct account *a) { struct fetch_maildir_data *data = a->data; return (data->total); } /* Initialise maildir fetch context. */ int fetch_maildir_state_init(struct account *a, struct fetch_ctx *fctx) { struct fetch_maildir_data *data = a->data; if (fetch_maildir_makepaths(a) != 0) return (FETCH_ERROR); if (ARRAY_EMPTY(data->paths)) { log_warnx("%s: no maildirs found", a->name); return (FETCH_ERROR); } data->index = 0; data->dirp = NULL; ARRAY_INIT(&data->unlinklist); /* Poll counts mails and exits. */ if (fctx->flags & FETCH_POLL) { if (fetch_maildir_poll(a) != 0) return (FETCH_ERROR); fetch_maildir_freepaths(a); return (FETCH_EXIT); } fctx->state = fetch_maildir_state_open; return (FETCH_AGAIN); } /* Next state. Move to next path. */ int fetch_maildir_state_next(struct account *a, struct fetch_ctx *fctx) { struct fetch_maildir_data *data = a->data; char *path; u_int i; for (i = 0; i < ARRAY_LENGTH(&data->unlinklist); i++) { path = ARRAY_ITEM(&data->unlinklist, i); log_debug2("%s: unlinking: %s", a->name, path); if (unlink(path) != 0) { log_warn("%s: %s: unlink", a->name, path); return (FETCH_ERROR); } xfree(path); } ARRAY_FREE(&data->unlinklist); if (data->index < ARRAY_LENGTH(data->paths)) data->index++; if (data->index == ARRAY_LENGTH(data->paths)) { if (!(fctx->flags & FETCH_EMPTY)) return (FETCH_BLOCK); fetch_maildir_freepaths(a); return (FETCH_EXIT); } fctx->state = fetch_maildir_state_open; return (FETCH_AGAIN); } /* Open state. */ int fetch_maildir_state_open(struct account *a, struct fetch_ctx *fctx) { struct fetch_maildir_data *data = a->data; char *path; path = ARRAY_ITEM(data->paths, data->index); /* Open the directory. */ log_debug2("%s: trying path: %s", a->name, path); if ((data->dirp = opendir(path)) == NULL) { log_warn("%s: %s: opendir", a->name, path); return (FETCH_ERROR); } fctx->state = fetch_maildir_state_mail; return (FETCH_AGAIN); } /* Mail state. Find and read mail file. */ int fetch_maildir_state_mail(struct account *a, struct fetch_ctx *fctx) { struct fetch_maildir_data *data = a->data; struct mail *m = fctx->mail; struct fetch_maildir_mail *aux; struct dirent *dp; char *path, *maildir, name[MAXPATHLEN]; struct stat sb; uintmax_t size; int fd; ssize_t n; path = ARRAY_ITEM(data->paths, data->index); restart: /* Read the next dir entry. */ dp = readdir(data->dirp); if (dp == NULL) { if (closedir(data->dirp) != 0) { log_warn("%s: %s: closedir", a->name, path); return (FETCH_ERROR); } data->dirp = NULL; fctx->state = fetch_maildir_state_next; return (FETCH_AGAIN); } if (ppath(name, sizeof name, "%s/%s", path, dp->d_name) != 0) { log_warn("%s: %s: printpath", a->name, path); return (FETCH_ERROR); } if (stat(name, &sb) != 0) { log_warn("%s: %s: stat", a->name, name); return (FETCH_ERROR); } if (!S_ISREG(sb.st_mode)) goto restart; /* Open the mail. */ if (mail_open(m, sb.st_size) != 0) { log_warn("%s: failed to create mail", a->name); return (FETCH_ERROR); } /* Got a valid entry, start reading it. */ log_debug2("%s: reading mail from: %s", a->name, name); size = sb.st_size; if (sb.st_size <= 0) { m->size = 0; return (FETCH_MAIL); } else if (size > SIZE_MAX || size > conf.max_size) { m->size = SIZE_MAX; return (FETCH_MAIL); } /* Open the file. */ if ((fd = open(name, O_RDONLY, 0)) == -1) { log_warn("%s: %s: open", a->name, name); return (FETCH_ERROR); } /* Add the tags. */ maildir = xbasename(xdirname(path)); default_tags(&m->tags, maildir); add_tag(&m->tags, "maildir", "%s", maildir); add_tag(&m->tags, "maildir_path", "%s", xdirname(path)); add_tag(&m->tags, "maildir_file", "%s", path); /* Add aux data. */ aux = xmalloc(sizeof *aux); strlcpy(aux->path, name, sizeof aux->path); m->auxdata = aux; m->auxfree = fetch_maildir_free; /* Read the mail. */ if ((n = read(fd, m->data, size)) == -1 || (size_t) n != size) { close(fd); log_warn("%s: %s: read", a->name, name); return (FETCH_ERROR); } close(fd); log_debug2("%s: read %ju bytes", a->name, size); m->size = size; return (FETCH_MAIL); } void fetch_maildir_desc(struct account *a, char *buf, size_t len) { struct fetch_maildir_data *data = a->data; char *maildirs; maildirs = fmt_strings("maildir ", data->maildirs); strlcpy(buf, maildirs, len); xfree(maildirs); } fdm-1.7+cvs20140912/MANUAL.in0000600000175000017500000016143611234120360013507 0ustar hawkhawkfdm ============================================================================ *** Introduction fdm is a program to fetch mail and deliver it in various ways depending on a user-supplied ruleset. Mail may be fetched from stdin, IMAP or POP3 servers, or from local maildirs, and filtered based on whether it matches a regexp, its size or age, or the output of a shell command. It can be rewritten by an external process, dropped, left on the server or delivered into maildirs, mboxes, to a file or pipe, or any combination. fdm is designed to be lightweight but powerful, with a compact but clear configuration syntax. It is primarily designed for single-user uses but may also be configured to deliver mail in a multi-user setup. In this case, it uses privilege separation to minimise the amount of code running as the root user. *** Table of contents ## Installation ## Quick start ## The configuration file %% Including other files %% Macros %% Testing macros %% Shell commands ## Invoking fdm %% Temporary files %% Command line arguments %% Running from cron %% The lock file %% Testing and debugging ## Fetching mail %% Mail tags %% POP3 and POP3S %% SSL certificate verification %% The .netrc file %% IMAP and IMAPS %% IMAP or POP3 over a pipe or ssh %% stdin and local mail %% From maildirs and mboxes %% Using NNTP and NNTPS %% New or old mail only ## Defining actions %% Drop and keep %% Maildirs %% Mboxes %% IMAP and IMAPS %% SMTP %% Write, pipe, exec and append %% stdout %% Rewriting mail %% Adding or removing headers %% Tagging %% Compound actions %% Chained actions ## Filtering mail %% Nesting rules %% Lambda actions %% The all condition %% Matched and unmatched %% Matching by account %% Matching a regexp %% Matching bracket expressions %% Matching by age or size %% Using a shell command %% Attachments %% Matching tags %% Using caches %% Cache commands ## Setting options ## Archiving and searching mail ## Using fdm behind a proxy ## Bug reports and queries ## Frequently asked questions ### Installation fdm depends on the Trivial Database library (TDB), available at: http://sourceforge.net/projects/tdb/ Ensure it is installed, then download the source tarball and build fdm with: $ tar -zxvf fdm-?.?.tar.gz $ cd fdm-?.? $ ./configure && make Then run 'make install' to install fdm to the default locations under /usr/local. The PREFIX environment variable may be set to specify an alternative installation location: $ export PREFIX=/opt # defaults to /usr/local $ sudo make install If being run as root, fdm requires a user named "_fdm" to exist. It will drop privileges to this user and its primary group. The user may be added on OpenBSD with, for example: # useradd -u 999 -s /bin/nologin -d /var/empty -g=uid _fdm It is not necessary to add a user if fdm is always started by a non-root user. fdm can be built to use PCRE rather than standard regexps. To do so, add -DPCRE to the make command: $ make -DPCRE Or PCRE=1 if using GNU make: $ make PCRE=1 ### Quick start A simple ~/.fdm.conf file for a single user fetching from POP3, POP3S and IMAP accounts and delivering to one maildir may look similar to: # Set the maximum size of mail. set maximum-size 128M # An action to save to the maildir ~/mail/inbox. action "inbox" maildir "%h/mail/inbox" # Accounts: POP3, POP3S and IMAP. Note the double escaping of the '\' # character in the password. If the port is omitted, the default # ("pop3", "pop3s", "imap" or "imaps" in the services(5) db) is used. account "pop3" pop3 server "my.pop3.server" user "my-username" pass "my-password-with-a-\\-in-it" account "pop3s" pop3s server "pop.googlemail.com" port 995 user "my-account@gmail.com" pass "my-password" # If the 'folder "my-folder"' argument is omitted, fdm will fetch mail # from the inbox. account "imap" imap server "my.imap.server" user "my-username" pass "my-password" folder "my-folder" # Discard mail from Bob Idiot. Note that the regexp is an extended # regexp, and case-insensitive by default. This action is a "lambda" or # unnamed action, it is defined inline as part of the match rule. match "^From:.*bob@idiot\\.net" in headers action drop # Match all other mail and deliver using the 'inbox' action. match all action "inbox" A simple initial configuration file without filtering, perhaps to replace fetchmail or getmail delivering to maildrop, may look similar to: # Set the maximum size of mail. set maximum-size 128M # Action to pipe directly to maildrop. action "maildrop" pipe "/usr/local/bin/maildrop" # Account definitions. account .... # Send all mail to maildrop. match all action "maildrop" To run fdm every half hour from cron, add something like this: */30 * * * * /usr/local/bin/fdm -l fetch See the fdm.conf(5) man page or the rest of this manual for more detail of the configuration file format. ### The configuration file fdm is controlled by its configuration file. It first searches for a .fdm.conf file in the invoking user's home directory. If that fails, fdm attempts to use /etc/fdm.conf. The configuration file may also be specified using the '-f' command line option, see the section on that subject below. This section gives an overview of the configuration file syntax. Further details of syntax, and specific keywords, are covered in later sections. The configuration file has the following general rules: - Keywords are specified as unadorned lowercase words: match, action, all. - Strings are enclosed in double quotes (") or single quotes ('). In double quoted strings, double quotes may be included by escaping them using the backslash character (\). Backslashes must also be escaped ("\\") - this applies to all such strings, including regexps and passwords. The special sequence '\t' is replaced by a tab character. In single quoted strings no escaping is necessary, but it is not possible to include a literal ' or a tab character. - Comments are prefixed by the hash character (#) and continue to the end of the line. - Whitespace is largely ignored. Lines may generally be split, concatenated or indented as preferred. - Lists are usually specified as 'singular item' or 'plural { item item }', for example: 'user "nicholas"', 'users { "nicholas" "bob" }'. The singular/plural distinction is not required, it is recommended only to aid readability: 'user { "nicholas "bob" }' is also accepted. - Regexps are specified as normal strings without additional adornment other than the "s (not wrapped in /s). All regexps are extended regexps. They are case insensitive by default but may be prefixed with the 'case' keyword to indicate case sensitivity is required. - Strings may be concatenated using plus: "a" + "b" is the same as "ab". This is most useful to wrap strings across multiple lines. Definition/option lines generally follow the following basic form: Example lines that may appear in a configuration file are: # This is a comment. set lock-types flock account "stdin" disabled stdin action "strip-full-disclosure" rewrite "sed 's/^\\(Subject:.*\\)\\[Full-disclosure\\] /\\1/'" match "^X-Mailing-List:.*linux-kernel@vger.kernel.org" in headers or "^(To:|Cc:):.*@vger.kernel.org" in headers action "linux-kernel" %%% Including other files The fdm configuration may be split into several files. Additional files may be referenced using the 'include' keyword: include "my-include-file.conf" include "/etc/fdm.d/shared-conf-1.conf" %%% Macros Macros may be defined and used in the configuration file. fdm makes a distinction between macros which may hold a number (numeric macros) and those that hold a string (string macros). Numeric macros are prefixed with the percentage sign (%) and string by the dollar sign ($). Macros are defined using the equals operator (=): %nummacro = 123 $strmacro = "a string" Macros may then be referenced in either a standalone fashion anywhere a string or number is expected, depending on the type of macro: $myfile = "a-file" include $myfile %theage = 12 match age < %theage action "old-mail" Or embedded in a string by enclosing the macro name in {}s: $myfile2 = "a-file2" include "/etc/${myfile2}" %anum = 57 include "/etc/file-number-%{anum}" Macros are not substituted in strings specified using single-quotes. %%% Testing macros The 'ifdef', 'ifndef' and 'endif' keywords may be used to include or omit sections of the configuration file depending on whether a macro is defined. An 'ifdef' is followed by a macro name (including $ or % type specifier) and if that macro exists, all following statements up until the next endif are evaluated (accounts created, rules added, and so on), otherwise they are skipped. 'ifndef' is the inverse: if the macro exists, the statements are skipped, otherwise they are included. An example is: ifdef $dropeverything match all action drop endif These keywords are particularly useful in conjunction with the '-D' command line option. Any statements between 'ifdef'/'ifndef' and 'endif' must still be valid syntax. %%% Shell commands The value of a shell command may be used at any point in the configuration file where fdm expects a string or number. Shell commands are invoked by enclosing them in $() or %(). They are executed when the configuration file is parsed and if $() is used, any output to stdout is treated as a literal string (as if the output was inserted directly in the file enclosed in double quotes); %() attempts to convert the output to a number. For example: $mytz = $(date +%Z) %two = %(expr 1 + 1) $astring = "abc" + $(echo def) Parts of the command within double quotes (") are subject to tag and macro replacement as normal (so it is necessary to use %% if a literal % is required, see the section on tags below); parts outside double quotes or inside single quotes are not. ### Invoking fdm fdm accepts a number of command line arguments and may be invoked as needed from the command line or by a mail transfer agent, such as sendmail, or at regular times using a program such as cron(8). %%% Temporary files As each mail is being processed, it is stored in a temporary file in /tmp, or if the TMPDIR environment variable exists in the directory it points to. fdm tries to queue a number of mails simultaneously, so that older can be delivered while waiting for the server to provide the next. The maximum length of the queue for each account is set by the 'queue-high' option (the default is two) and the maximum mail size accepted by the 'maximum-size' option (the default is 32 MB). In addition, the 'rewrite' action requires an additional temporary mail. Although fdm will fail rather than dropping mail if the disk becomes full, users should bear in mind the possibility and set the size of the temporary directory and the fdm options according to their needs. %%% Command line arguments The fdm command has the following synopsis: fdm [-klmnqv] [-f conffile] [-u user] [-a account] [-x account] [-D name=value] [fetch | poll | cache ...] The meaning of the flags are covered in the fdm(1) man page, but a brief description is given below. The flags are also mentioned at relevant points in the rest of this document. Flag Meaning -k Keep all mail (do not delete it from the server). This is useful for testing delivery rules without risking mail ending up permanently in the wrong place. -l Log to syslog(3) using the 'mail' facility rather than outputting to stderr. -m Ignore the lock file. -n Run a syntax check on the configuration file and exit without fetching any mail. -q Quiet mode. Don't print anything except errors. -v Print verbose debugging output. This option may be specified multiple times for increasing levels of verbosity. Useful levels are -vv to display the result of parsing the configuration file, and -vvvv to copy all traffic to and from POP3 or IMAP servers to stdout (note that -l disables this behaviour). -f conffile Specify the path of the configuration file. -u user Use 'user' as the default user for delivering mail when started as root. -a account Process only accounts with a name matching the given pattern. Note that fnmatch(3) wildcards may be used to match multiple accounts with one option, and that the option may be specified multiple times. -x account Process all accounts except those that match the given pattern. Again, fnmatch(3) wildcards may be used, and the -x option may be specified multiple times. -D name=value Define a macro. The macro name must be prefixed with '$' or '%' to indicate if it is a string or numeric macro. Macros defined on the command line override any macros with the same name defined in the configuration file. If -n is not specified, the flags must be followed by one of the keywords 'fetch' or 'poll' or 'cache'. The 'fetch' keyword will fetch and deliver mail, the 'poll' keyword print an indication of how many mails are present in each account, and the 'cache' keyword is followed by one of a set of cache commands used to manipulate caches from the command-line (see the sections on caches below). 'fetch' or 'poll' or 'cache' may be abbreviated. Examples: $ fdm -v poll $ fdm -vvnf /etc/my-fdm.conf $ fdm -lm -a pop3\* fetch $ fdm -x stdinacct fetch # fdm -u nicholas -vv f %%% Running from cron To fetch mail regularly, fdm must be run from cron. This line in a crontab(5) will run fdm every 30 minutes: */30 * * * * /usr/local/bin/fdm -l fetch The '-l' option sends fdm's output to syslog(3) rather than having cron mail it. To keep a closer eye, adding '-v' options and removing '-l' will have debugging output mailed by cron, or, using a line such as: */30 * * * * fdm -vvvv fetch >>/home/user/.fdm.log 2>&1 Will append extremely verbose fdm output to the ~/.fdm.log file. Note that this log file can become pretty large, so another cronjob may be required to remove it occasionally! %%% The lock file fdm makes use of a lock file to prevent two instances running simultaneously. By default, this lock file is .fdm.lock in the home directory of the user who runs fdm, or /var/db/fdm.lock for root. This default may be overridden in the configuration file with the 'set lock-file' command: set lock-file "/path/to/my/lock-file" Or disabled altogether by being set to the empty string: set lock-file "" The '-m' command line option may be used to force fdm to ignore the lock file and run regardless of its existence and without attempting to create it. %%% Testing and debugging fdm has some features to assist with testing and debugging a ruleset: The '-n' command line option. This is particularly useful in conjunction with '-vv', for example: $ cat test.conf account "pop3" pop3 server "s" user "u" pass "p" action "rw" rewrite "sed 's/\\(Subject:.*\\)\\[XYZ\\]/\1/'" action "mbox" mbox "%h/INBOX" match all actions { "rw" "mbox" } $ fdm -vvnf test.conf version is: fdm 0.6 (20061204-1433) starting at: Tue Dec 5 15:45:41 2006 user is: nicholas, home is: /home2/nicholas loading configuration from test.conf added account: name=pop3 fetch=pop3 server "s" port pop3 user "u" added action: name=rw deliver=rewrite "sed 's/\(Subject:.*\)\[XYZ\]/1/'" added action: name=mbox deliver=mbox "%h/INBOX" finished file test.conf added rule: actions="rw" "mbox" matches=all configuration loaded locking using: flock headers are: "to" "cc" domains are: "yelena" using tmp directory: /tmp Looking at the output, the parsed strings used by fdm can be seen, and it is possible to spot that an escape character has been missed in the command. If '-vvvv' is used, fdm will print all data sent to and received from remote servers to stdout. Note that this is disabled if the '-l' option is given, and includes passwords, usernames and hostnames unmodified. The 'fdm-sanitize' script provided with fdm may be used to remove passwords and usernames from this output, either while it is being collected: fdm -vvvv -a testacct f 2>&1|./fdm-sanitize|tee my-output Or afterwards: ./fdm-sanitize my-output Since fdm fetches multiple accounts simultaneously, which may intersperse debugging output, it is recommended to fetch each account seperately if running the output through fdm-sanitize. If this is not done, it may not be able to detect all usernames or passwords. The '-k' command line option (and the 'keep' keywords on actions and accounts, covered later) prevent fdm from deleting any mail after delivery. This may be used to perform any number of test deliveries without risk of losing mail. ### Fetching mail fdm fetches mail from a set of 'accounts', defined using the 'account' keyword. Each account has a name, a type, a number of account specific parameters and a couple of optional flags. The general form is: account [] [disabled] [] [keep] The item is a string by which the account is referred in filtering rules, log output and for the '-a' and '-x' command line options. The portion specifies the default users to use when delivering mail fetched from this account as root. It has the same syntax as discussed in detail in the section below on defining actions. If the optional 'disabled' keyword is present, fdm ignores the account unless it is specified on the command line using the '-a' flag. The optional 'keep' keyword instructs fdm to keep all mail from this account (not delete it from the server) regardless of the result of the filtering rules. The item may be one of: 'pop3', 'pop3s', 'imap', 'imaps', 'stdin', 'maildir' or 'maildirs'. %%% Mail tags As mail is processed by fdm, it is tagged with a number of name/value pairs. Some tags are added automatically, and mail may also be tagged explicitly by the user (see the later tagging section). Tags may be inserted in strings in a similar manner to macros, except tags are processed when the string is used rather than always as the configuration file is parsed. A tag's value is inserted by wrapping its name in %[], for example: match string "%[account]" to "myacct" action "myacctact" Most of the default tags have a single-letter shorthand which removes the needs for the []s: match string "%a" to "myacct" action "myacctact" Including a nonexistent tag in a string is equivalent to including a tag with an empty value, so "abc%[nonexistent]def" will be translated to "abcdef". The automatically added tags are: Name Shorthand Replaced with account %a The name of the account from which the mail was fetched. home %h The delivery user's home directory. uid %n The delivery user's uid. action %t The name of the action the mail has matched. user %u The delivery user's username. hour %H The current hour (00-23). minute %M The current minute (00-59). second %S The current second (00-59). day %d The current day of the month (00-31). month %m The current month (01-12). year %y The current year as four digits. year2 The current year as two digits. dayofweek %W The current day of the week (0-6, Sunday is 0). dayofyear %Y The current day of the year (000-365). quarter %Q The current quarter (1-4). rfc822date The current time in RFC822 date format. mail_hour The hour from the mail's date header, converted to local time, if it exists and is valid, otherwise the current time. mail_minute The minute from the mail's date header. mail_second The second from the mail's date header. mail_day The day from the mail's date header. mail_month The month from the mail's date header. mail_year The year from the mail's date header as four digits. mail_year2 The same as two digits. mail_rfc822date The mail date in RFC822 format. hostname The local hostname. In addition, the shorthand %% is replaced with a literal %, and %1 to %9 are replaced with the result of any bracket expressions in the last regexp (see later section on regexps). A leading ~ or ~user is expanded in strings where a path or command is expected. Some accounts add additional tags, discussed below. Tags are replaced in almost all strings (including those in single-quotes!), some when the configuration file is parsed and some when the string is used. %%% POP3 and POP3S Mail may be fetched from a POP3 account. A POP3 account is defined by specifying the following parameters: the server host and optionally port, and optionally the user name and password. If the port is not specified, the default port ('pop3' in the services(5) database) is used. If the user name, password, or both is omitted, fdm attempts to look it up the .netrc file, see the next section for details. Examples of a POP3 account definition are: account "pop3acct" pop3 server "pop.isp.com" user "bob" pass "pass" account "gmx" pop3 server "pop.gmx.net" port 110 user "jim" pass "pass" account "acct" pop3 server "10.0.0.1" port "pop3" user "nicholas" keep account "lycos" disabled pop3 server $localserver port 10110 pass "password" Note that the server string is enclosed in double quotes even if it is an IP, and don't forget to escape any " and \ characters in passwords! fdm will attempt to use APOP to obscure the password, if the server offers it. If the server advertises itself as supporting APOP but subsequently refuses to accept it, fdm will not retry with a cleartext password. Use of APOP can be disabled for an account using the 'no-apop' flag, for example: account "acct" pop3 server "server" user "bob" pass "pass" no-apop POP3S is specified in exactly the same way, except using the 'pop3s' keyword for the type, and the default port is 'pop3s' rather than 'pop3': account "pop3sacct" pop3s server "pop.isp.com" user "bob" pass "pass" POP3 accounts automatically tag mail with 'server' and 'port' tags, with the value of the server and port attributes exactly as specified in the account definition. A 'server_uid' tag is also added with the server unique id (UIDL). POP3 adds 'lines', 'body_lines' and 'header_lines' tags with the number of lines in the complete mail and its body and header. These tags are not updated to reflect any changes made to the mail by fdm rules. %%% SSL certificate verification fdm can verify SSL certificates before collecting mail from an SSL server. This is enabled globally with the 'verify-certificates' option: set verify-certificates And may be disabled per-account using the 'no-verify' keyword (this applies to both POP3S and IMAPS accounts): account "pop3sacct" pop3s server "pop.isp.com" no-verify For an introduction to SSL, see: http://httpd.apache.org/docs/2.0/ssl/ssl_intro.html A cert bundle is required to verify SSL certificate chains. For more information see: http://lynx.isc.org/current/README.sslcerts A pregenerated bundle is available courtesy of the MirOS project: http://cvs.mirbsd.de/src/etc/ssl.certs.shar %%% The .netrc file If the user name or password is omitted in POP3 or IMAP account definitions, fdm will attempt to look it up in the .netrc file in the invoking user's home directory. The .netrc file format is shared with ftp(1) and some other programs. It consists of a number of 'machine' sections and optionally one 'default' section containing a username ('login') and password for that host. fdm accepts entries only if the machine name matches the POP3 or IMAP server string exactly. If no matches are found and a 'default' section exists, it is used. An example .netrc file is: machine "my.mail-server.com" login "nicholas" password "abcdef" machine "pop.googlemail.com" password "pass1" default login "bob" password "moo" fdm will abort if the .netrc file is world-writable or world-readable. %%% IMAP and IMAPS IMAP and IMAPS accounts are defined using exactly the same syntax as for POP3 and POP3S, aside from using the 'imap' or 'imaps' keywords and that the default port is 'imap' or 'imaps'. There is also an additional, optional 'folders' option to specify the folders from which mail should be fetched. If omitted, fdm defaults to the inbox. Note that with IMAP and IMAPS, mail is still removed from the server unless the 'keep' option is given, or the '-k' command line option used. Examples of IMAP and IMAPS accounts include: account "imapacct" imap server "imap.server.ca" user "bob" pass "pass" account "oldimap" disabled imaps server "192.168.0.1" port 10993 user "nicholas" pass "pass" folders { "Saved" "MyStuff" } account "workspam" disabled imap server "my-work.ath.cx" user "Nicholas" folder "Junk" By default, fdm prefers the CRAM-MD5 authentication method, since no passwords are sent in the clear. If the server does not advertise CRAM-MD5 capability, the older LOGIN method is used. For IMAPS connections (which use SSL), the LOGIN method is just as secure. Either of these methods may be disabled with the 'no-cram-md5' and 'no-login' options. As with POP3, IMAP adds the 'server', 'port', 'server_uid' and the three line count tags to mail. %%% IMAP or POP3 over a pipe or ssh Mail may be fetched using IMAP or POP3 via a pipe. This is particularly useful for fetching mail over ssh using public keys. For IMAP, a user and password may be supplied, but fdm will only use them if the server asks. If the connection is preauthenticated, the user and password are unnecessary. For POP3, a user and password must be supplied as usual: due to the lack of server name, it cannot be read from the .netrc file. Communication takes place via the pipe program's stdin and stdout. If any output is found on stderr, fdm will print it (or log it with '-l'). Examples are: account "imapssh" imap pipe "ssh jim@myhost /usr/local/libexec/imapd" account "imapssh2" imap pipe "/usr/bin/whatever" user "bob" pass "bah" account "pop3local" pop3 pipe "/usr/local/bin/ipop3d" user "me" pass "foo" %%% stdin and local mail fdm may be configured to fetch mail from stdin, by specifying an account of type 'stdin', for example: account "stdin" disabled stdin This is most useful to have fdm behave as a mail delivery agent. To configure it for single-user use with sendmail, the simplest method it to add: "|/usr/local/bin/fdm -m -a stdin fetch" To the user's ~/.forward file (including the double quotes). Note the use of '-m' to prevent stdin delivery from interfering with any normal cronjob, and '-a' to specify that only the disabled "stdin" account should be fetched. stdin accounts add the three line count tags described in the POP3 section. %%% From maildirs and mboxes Fetching from maildirs allows fdm to be used to filter mail on the local machine. This is covered more detail in the later section on archiving and searching. Maildir accounts are specified as follows: account "mymaildir" maildir "/path/to/dir" account "mymaildirs" maildirs { "/path/to/dir1" "/path/to/dir2" } Shell glob wildcards may be included in the path names to match multiple maildirs, but every directory found must be a valid maildir. Maildir accounts tag mail with a 'maildir' tag which is the basename of the maildir. Fetching from mboxes is similar: account "mybox" mbox "/path/to/mbox" account "mymboxes" mboxes { "/path/to/mbox1" "/path/to/mbox2" } Note that if an mbox is modified (mail is dropped from it), sufficient disk space is required to create a temporary copy of the entire mbox. %%% Using NNTP and NNTPS fdm can fetch news messages from a news server using NNTP or NNTPS. News accounts are specified like so: account "news1" nntp server "news.server.sk" port 119 group "comp.unix.bsd.openbsd.misc" cache "%h/.fdm.cache/%[group]" account "mynews" nntps server "ssl.news.server" port "nntps" user "myuser" pass "mypass" groups { "alt.test" "alt.humor.best-of-usenet" } cache "%h/.fdm.cache" The cache is a file used to store details of the last article fetched. If only one group is supplied in the account definition, %[group] tags are replaced by the name of the group in the cache path. If multiple groups are provided, %[group] is removed. Note that whether a message is kept or deleted is irrelevent to NNTP, articles are always left on the server. The index and message-id of the last article is recorded in the cache file so that older articles are skipped when the a newsgroup is again fetched. This happens regardless of any 'keep' keywords or the '-k' command line option. As with POP3 and IMAP, NNTP accounts add the 'server' and 'port' tags to mail. In addition, a 'group' tag is added with the group name. This can ensure articles are matched purely on the group they are fetched from (trying to do this using headers is unreliable with cross-posted articles). For example: match account "news" { match string "%[group]" to "comp.lang.c" action "news-%[group]" match string "%[group]" to "comp.std.c" action "news-%[group]" match all action drop } %%% New or old mail only With POP3 and IMAP, fdm can be set up to fetch only new or old mail. For POP3 this is achieved by recording the current state of the server in a cache file, which is updated as each mail is fetched. For IMAP it makes use of the 'seen' server flag which is updated by the server after each mail is fetched. These options are specified as in the following examples. For POP3: account "name" pop3 server "blah" new-only cache "~/.fdm-pop3-cache" account "acct" pop3s server "my-server" user "bob" new-only cache "my-server-pop3-cache" no-apop And for IMAP: account "imap" imap server "blah" new-only account "sslimap" imaps server "imaps.somewhere" user "user" pass "pass" old-only no-verify Note that currently, when using this with IMAP, the server is permitted to flag the mail as 'seen' before fdm has successfully delivered it, so there is no guarantee that mail so marked has been delivered, only that it has been fetched. ### Defining actions An action is a particular command to execute on a mail when it matches a filtering rule (see the next section on filtering mail). Actions are named, similar to accounts, and have a similar form: action [] The item may be either: - the keyword 'user' followed by a single username string or uid, such as: user "nicholas" user "1000" - the keyword 'users' followed by a list of users in {}s, for example: users { "1001" "nicholas" } If users are specified, the action will be run once for each user, with fdm changing to that user before executing the action. Note that fdm will execute the action once for each user even when not started as root, but will not be able to change to the user. The user keyword is primarily of use in multiuser configurations. If users are present on an action, they override any specified by the account definition. Users are looked up in the Unix passwd file or optionally (if fdm is built with "make -DCOURIER" or "make COURIER=1") using courier-authlib. The order of lookups may be specified with the lookup-order option: set lookup-order courier passwd set lookup-order passwd If running as root and no user is specified on either the action or on the filtering rule (see the section on filtering below), the default user is used, see the '-u' command line option and the 'default-user' option in the setting options section %%% Drop and keep The simplest actions are the 'drop' and 'keep' actions. They have no parameters and are specified like this: action "mydropaction" drop action "mykeepaction" keep The 'drop' action arranges for mail to be dropped when rule evaluation is complete. Note that using 'drop' does not stop further evaluation if the filtering rule contains a 'continue' keyword, and it may be overridden by a 'keep' option on the account or by the '-k' flag on the command line. The 'keep' action is similar to 'drop', but it arranges for the mail to be kept once rule evaluation is complete, rather than dropped. %%% Maildirs Mails may be saved to a maildir through a 'maildir' action, defined like so: action "mymaildiraction" maildir "/path/to/maildir" If any component of the maildir path does not exist, it is created, unless the no-create option is specified. Mails saved to a maildir are tagged with a 'mail_file' tag containing the full path to the file in which they were saved. %%% Mboxes An action to deliver to an mbox is defined in the same way as for a maildir: action "mymboxaction" mbox "/path/to/mbox" The same % tokens are replaced in the path. If the mbox does not exist, it is created. Mboxes may optionally be gzip compressed by adding the 'compress' keyword: action "mymboxaction" mbox "/path/to/mbox" compress fdm will append .gz to the mbox path (if it is not already present) and append compressed data. If the mbox exists but is not already compressed, uncompressed data will be appended. As with maildirs, if any component of the mbox path does not exist, it is created, unless the no-create option is set. Mails saved to an mbox are tagged with an 'mbox_file' tag with the path of the mbox. %%% IMAP and IMAPS An action may be defined to store mail in an IMAP folder. The specification is similar to the IMAP action. A server host and optionally port (default 'imap' or 'imaps') must be specified. A username and password may be supplied; if they are omitted, fdm will attempt to find a .netrc entry. Examples include: action "myimapaction" imap server "imap.server" action "myimapaction" imaps server "imap.server" port "8993" user "user" pass "pass" folder "folder" action "myimapaction" imaps server "imap.server" user "user" pass "pass" no-verify no-login %%% SMTP An action may be defined to pass mail on over SMTP. The server host must be specified and optionally the port and string to pass to the server with the RCPT TO and MAIL FROM commands. If the port is not specified it defaults to "smtp". Examples include: action "mysmtpaction" smtp server "smtp.server" action "mysmtpaction" smtp server "smtp.server" port 587 action "mysmtpaction" smtp server "smtp.server" port "submission" from "bob@server.com" action "mysmtpaction" smtp server "smtp.server" to "me@somewhere" %%% Write, pipe, exec and append Actions may be defined to write or append a mail to a file, to pipe it to a shell command, or merely to execute a shell command. The append action appends to and write overwrites the file. % tokens are replaced in the file or command as for maildir and mbox actions. Examples are: action "mywriteaction" write "/tmp/file" action "myappendaction" append "/tmp/file" action "mypipeaction" pipe "cat > /dev/null" action "domaildirexec" exec "~/.fdm.d/my-special-script %[mail_file]" Pipe and exec commands are run as the command user (by default the user who invoked fdm). %%% stdout fdm can write mails directly to stdout, using the 'stdout' action: action "so" stdout %%% Rewriting mail Mail may be altered by passing it to a rewrite action. This is similar to the pipe action, but the output of the shell command to stdout is reread by fdm and saved as a new mail. This is useful for such things as passing mail through a spam filter or removing or altering headers with sed. Note that rewrite only makes sense on filtering rules where the continue keyword is specified, or where multiple actions are used (see the next section for details of this). Possible rewrite action definitions are: action "myspamaction" rewrite "bmf -p" action "mysedaction" rewrite "sed 's/x/y/'" %%% Adding or removing headers Simple actions are provided to add a header to a mail: action "lines" add-header "Lines" value "%[lines]" Or to remove all instances of a header from mail: action "del-ua" remove-header "user-agent" action "rmhdr" remove-header "x-stupid-header" action "remove-headers" remove-headers { "X-*" "Another-Header" } %%% Tagging Mails may be assigned one of more tags manually using the tag action type. For example, match account "my*" action tag "myaccts" match "^User-Agent:[ \t]*(.*)" action tag "user-agent" value "%1" The tag is attached to the mail with the specified value, or no value if none is provided. %%% Compound actions Compound actions may be defined which perform multiple single actions. They are similar to standard single actions but multiple actions are provided using {}. For example, action "multiple" { add-header "X-My-Header" value "Yay!" mbox "mbox2" } action "myaction" users { "bob" "jim" } { rewrite "rev" maildir "%h/%u's maildir" } Compound action are executed from top-to-bottom, once for each user. Note that the effects are cumulative: the second example above would deliver a mail rewritten once to 'bob' and rewritten again (ie, twice) to 'jim'. If this is not desired, seperate actions must be used. %%% Chained actions An action may call other named actions by reusing the 'action' keyword: action "abc" action "def" action "an_action" { rewrite "rev" action "another_action" action "yet_more_actions" } There is a hard limit of five chained actions in a sequence to prevent infinite loops. ### Filtering mail Mail is filtered by defining a set of filtering rules. These rules tie together mail fetched from an account and passed to one or more actions. Rules are evaluated from top-to-bottom of the file, and evaluation stops at the first matching rule (unless the continue keyword is specified). The general form of a filtering rule is: match [] [continue] The optional item is specified as for an action definition. If users are specified on a filtering (match) rule, they override any specified on the action or account. The item is set of conditions against which the match may be specified, each condition returns true or false. Conditions are described in the next few sections. Aside from the 'all' condition, which is a special case, conditions may be chained as an expression using 'and' and 'or', in which case they are evaluated from left to right at the same precedence, or prepended with 'not' to invert their outcome. The item is a list of actions to execute when this rule matches. It is in the same list format: 'action "name"' or 'actions { "name1" "name2" }'. It may also be a lambda (inline) action, see the section below. If a rule with the 'continue' keyword matches, evaluation does not stop after the actions are executed, instead subsequent rules are matched. %%% Nesting rules Filtering rules may be nested by using the special form: match [] { match ... } If the conditions on the outer rule match, the inner rules are evaluated. If none of the inner rules match (or they all specify the 'continue' keyword) evaluation continues outside to rules following the nested rule, otherwise it stops. %%% Lambda actions Lambda actions are unnamed actions included inline as part of the filtering rule. This can be convenient for actions which do not need to be used multiple times. Lambda actions are specified as a combination of the rule and an action definition. For example: match all action maildir "mymaildir" match all actions { rewrite "rev" tag "reversed" } continue %%% The all condition The all condition matches all mail. Examples include: match all action "default" match all rewrite "rewaction" continue %%% Matched and unmatched The matched and unmatched conditions are used to match mail that has matched or has not matched previous rules and been passed on with the 'continue' keyword. For example, match "myregexp" action "act1" continue # This rule will match only mails that also matched the first. match matched action "act2" # This rule will match only mails that matched neither of the first two. match unmatched action "act3" %%% Matching by account The account condition matches a list of accounts from which the mail was fetched. It is specified as either a single account ('account "name"') or a list of accounts ('accounts { "name1" "name2" }'). fnmatch(3) wildcards may also be used. Examples include: match "blah" accounts { "pop3" "imap" } action "go!" match matched and account "myacc" action drop %%% Matching a regexp Matching against a regexp is the most common form of condition. It takes the following syntax: [case] [in headers|in body] The 'case' keyword instructs fdm to match the regexp case sensitively rather than the default of case insensitivity. The 'in headers' or 'in body' keywords make fdm search only the headers or body of each mail, the default is to match the regexp against the entire mail. Any multiline headers are unwrapped onto a single line before matching takes place and the process reversed afterwards. The regexp itself is an extended regexp specified as a simple string, but care must be taken to escape \s and "s properly. Examples include: match "^From:.*bob@bobland\\.bob" in headers and account "pop3" action "act" match ".*YAHOO.*BOOTER.*" in body action "junk" %%% Matching bracket expressions The results of any bracket expressions within the last regexp match are remembered, and may be made use of using the 'string' condition, or used to construct an action name, maildir or mbox path, etc. The bracket expressions may be substituted using the %0 to %9 tokens. For example, match "^From:.*[ \t]([a-z]*)@domain" in headers action "all" continue match string "%1" to "bob.*" action "bobmail" match "^From:.*[ \t]([a-z]*)@domain" in headers action "all" continue match all action "%1mail" This is particularly useful in combination with nested rules (see later): bracket expressions in a regexp on the outer rule may be compared on inner rules. Note that %0 to %9 are used only for 'regexp' rules. Regexps that are part of 'command' rules use the 'command0' to 'command9' tags. %%% Matching by age or size Mail may be matched based on its age or size. An age condition is specified as follows: age [<|>] [hours|minutes|seconds|days|months|years] If '<' is used, mail is matched if it is younger than the specified age. If '>', if it is older. The item may be a simple number of seconds, or suffixed with a unit. Examples are: match age < 3 months actions { "act1" "act2" } match age > 100 hours action "tooold" The age is extracted from the 'Date' header, if possible. To match mails for which the header was invalid, the following form may be used: match age invalid action "baddate" The size condition is similar: size [<|>] [K|KB|kilobytes...] Where is a simple number in bytes, or suffixed with 'K', 'M' or 'G' to specify a size in kilobytes, megabytes or gigabytes, such as: match size < 1K action "small" match size > 2G action "whoa" %%% Using a shell command Mail may be matched using the result of a shell command. This condition follows the form: [exec|pipe] returns (, [case] ) If 'exec' is used, the command is executed. If 'pipe', the mail is piped to the command's stdin. The is a simple string. % tokens are replaced as normal. Any of the or or both may be specified. The is a simple number which is compared against the return code from the command, the is a regexp that is matched case insensitively against each line output by the command on stdout. The result of any bracket expressions in the stdout regexp are saved as 'command0' to 'command9' tags on the mail. Any output on stderr is logged by fdm, so 2>&1 must be included in the command in order to apply the regexp to it. Examples: match exec "true" (0, ) action "act" match not pipe "grep Bob" (1, ) action "act" match pipe "myprogram" (, "failed") actions { "act1" "act2" } match exec "blah" (12, "^Out") action "meep" %%% Attachments There are five conditions available to filter based on the size, quantity, type and name of attachments. They are all prefixed with the 'attachment' keyword. Two compare the overall number of attachments: The 'attachment count' conditions matches if the number of attachments is equal to, not equal to, less than or greater than the specified number: match attachment count == 0 action "action" match attachment count != 10 action "action" match attachment count < 2 action "action" match attachment count > 7 action "action" The 'attachment total-size' condition is similar, but compares the total size of all the attachments in a mail: match attachment total-size < 4096 kilobytes action "action" match attachment total-size > 1M action "action" There are also three conditions which matches if any individual attachment fulfils the condition: 'any-size' to match if any attachment is less than or greater than the given size, and 'any-type' and 'any-name' which compare the attachment MIME type and name attribute (if any) using fnmatch(3): match attachment any-size < 2K action "action" match attachment any-type "*/pdf" action "action" match attachment any-name "*.doc" action "action" %%% Matching tags The existence of a tag may be tested for using the 'tagged' condition: match tagged "mytag" action "a" match tagged "ohno" and size >1K action drop Or the tags value matched using the 'string' match type (in a similar way to matching bracket expressions): match string "%[group]" to "comp.lang.c" action "clc" match string "%u" to "bob" action "bob" %%% Using caches fdm has builtin support for maintaining a cache of string keys, including appending to a cache, checking if a key is present in a cache, and expiring keys from a cache once they reach a certain age. These caches should not be confused with the NNTP cache file. Key caches are referenced by filename and must be declared before use: cache "%h/path/to/cache" cache "~/.fdm.db" expire 1 month If the expiry time is not specified, items are never expired from the cache. Once declared, keys may be added to the cache with the 'add-to-cache' action: match all action add-to-cache "~/my-cache" key "%[message_id]" Or removed with the 'remove-from-cache' action: match all action remove-from-cache "~/my-cache" key "%[message_id]" And the existence of a key in the cache may be tested for using the 'in-cache' condition: match in-cache "~/my-cache" key "%[message_id]" action "foundincache" Any string may be used as key, but the message-id is most often useful. Note that the key may not be empty, so care must be taken with messages without message-id (such as news posts fetched with NNTP). Caches may be used to elimate duplicate messages using rules similar to those above: $db = "~/.fdm-duplicates.db" $key = "%[message_id]" cache $db expire 2 weeks match not string $key to "" { match in-cache $db key $key action maildir "%h/mail/duplicates" match all action add-to-cache $db key $key continue } %%% Cache commands fdm includes a number of commands to manipulate caches from the command-line. These are invoked with the 'cache' keyword followed by a command. The following commands are supported: cache add cache remove These add or remove as a key in the cache . cache list [] This lists the number of keys in a cache, or in all caches declared in the configuration file if is omitted. cache dump This dumps the contents of the cache to stdout. Each key is printed followed by a space and the timestamp as Unix time. cache clear Delete all keys from a cache. Examples: $ fdm cache list /export/home/nicholas/.fdm.d/duplicates: 4206 keys $ touch my-cache $ fdm cache dump my-cache $ fdm cache add my-cache test $ fdm cache dump my-cache test 1195072403 ### Setting options fdm has a number of options that control its behaviour. These are defined using the set command: set