Courier-MTA is a modular mail system that features various kinds of mail filtering. This page is an example of an alternative usage of the localmailfilter subsystem, using C and shell scripts.
Local filters constitute a way of filtering mail according to recipients. Unlike global filters, local filters only do incoming mail; that is, messages destined to the local server. Normally, maildrop is used for this task.
Maildrop is best known for being used as a delivery agent; that is, in delivery mode. Let's quickly recall that there is a sysconfdir/maildrop 1-line configuration file that contains the path of the delivery agent, as an alternative to the DEFAULTDELIVERY setting. When called in delivery mode, maildrop reads the files sysconfdir/maildroprc and $HOME/.mailfilter. Local mail filtering happens before a message is accepted, while the SMTP dialog is underway. There is a different 1-line configuration file, sysconfdir/maildropfilter, that contains the path of the filtering executable. As documented in localmailfilter, it can point to the same maildrop binary. The example described here, instead, points to a tiny executable that runs a shell script.
The interesting point of running a shell script is that one can run exactly the same script for delivery. For example, one can handle aliases dynamically having aliasdir/.courier-default consisting of the single line:
Here is the file that I posted on the courier-users list on Friday 27 October 2007. You can download the C code or read it below. To install it, compile the code (please use -DNDEBUG for production, as mentioned in the top comment) and configure sysconfdir/maildropfilter with the relevant path. Mind permissions. This example code is in the public domain.
|| rcptfilter.sh
Download the example source
001: /*
002: * rcptfilter.c - written by vesely in milan on 8 aug 2006
003: *
004: * Run rcptfilter.sh
005: * gcc -W -O -DNDEBUG -o /usr/local/sbin/rcptfilter rcptfilter.c
006: * gcc -W -Wall -Wno-parentheses -O0 -g -o rcptfilter rcptfilter.c
007: */
008:
009: #define SCRIPTFILE "rcptfilter.sh"
010:
011: static char const usage[] =
012: "usage:\n"
013: "\trcptfilter -h\n"
014: "\trcptfilter -D uid/gid -M rcptfilter[-ext] ...\n"
015: "The first format is only useful to print this note.\n"
016: "The second format is used by Courier's local output module if the\n"
017: "maildropfilter configuration file contains the path of this executable.\n"
018: "In this case, rcptfilter changes directory to HOME and looks for file\n"
019: "\"" SCRIPTFILE "\" and runs it (with null input and output, logged error)\n"
020: "and then returns an exit code of 0 if the script exits rc < 50, 1 otherwise.\n"
021: "The script will have argument $1 set to \"RCPT\" and the other arguments\n"
022: "set to whatever was passed to rcptfilter as ellipsis (...). The variables\n"
023: "LOCAL and HOST will be set from ext. The rest of the environment remains.";
024:
025: #include <stdio.h>
026: #include <string.h>
027: #include <stdlib.h>
028: #include <sys/types.h>
029: #include <sys/stat.h>
030: #include <sys/wait.h>
031: #include <fcntl.h>
032: #include <unistd.h>
033: #include <signal.h>
034: #include <syslog.h>
035: #include <ctype.h>
036: #include <errno.h>
037: #include <assert.h>
038:
039: static volatile int
040: signal_child = 0,
041: signal_timed_out = 0,
042: signal_break = 0;
043:
044: static void sig_catcher(int sig)
045: {
046: #if !defined(NDEBUG)
047: char buf[80];
048: unsigned s = snprintf(buf, sizeof buf,
049: "rcptfilter[%d]: received signal %d\n",
050: (int)getpid(), sig);
051: if (s >= sizeof buf)
052: {
053: buf[sizeof buf - 1] = '\n';
054: s = sizeof buf;
055: }
056: write(2, buf, s);
057: #endif
058: switch(sig)
059: {
060: case SIGALRM:
061: signal_timed_out = 1;
062: break;
063:
064: case SIGHUP:
065: case SIGPIPE:
066: case SIGINT:
067: case SIGQUIT:
068: case SIGTERM:
069: signal_break = 1;
070: break;
071:
072: case SIGCHLD:
073: signal_child = 1;
074: break;
075:
076: default:
077: break;
078: }
079: }
080:
081: #if 0
082: static void reset_signal(void)
083: {
084: struct sigaction act;
085: memset(&act, 0, sizeof act);
086: sigemptyset(&act.sa_mask);
087: act.sa_handler = SIG_DFL;
088:
089: sigaction(SIGALRM, &act, NULL);
090: sigaction(SIGPIPE, &act, NULL);
091: sigaction(SIGINT, &act, NULL);
092: sigaction(SIGTERM, &act, NULL);
093: sigaction(SIGHUP, &act, NULL);
094: sigaction(SIGCHLD, &act, NULL);
095: }
096: #endif
097:
098: static void set_signal(void)
099: {
100: struct sigaction act;
101: memset(&act, 0, sizeof act);
102: sigemptyset(&act.sa_mask);
103:
104: act.sa_handler = sig_catcher;
105: sigaction(SIGALRM, &act, NULL);
106: sigaction(SIGPIPE, &act, NULL);
107: sigaction(SIGINT, &act, NULL);
108: sigaction(SIGTERM, &act, NULL);
109: sigaction(SIGHUP, &act, NULL);
110: sigaction(SIGCHLD, &act, NULL);
111: }
112:
113: static void addtoenv(char const *name, char const *value)
114: {
115: char *freeonexit = (char*)malloc(strlen(name) + strlen(value) + 1);
116: if (freeonexit)
117: putenv(strcat(strcpy(freeonexit, name), value));
118: }
119:
120: static int run_script(char const *ext, char *argv[], char const *home)
121: {
122: int rtc = 0;
123: char *local = strdup(ext);
124: if (local)
125: {
126: int err[2];
127: char *host = strchr(local, '@');
128:
129: if (host)
130: *host++ = 0;
131: else
132: host = "";
133: addtoenv("HOST=", host);
134: addtoenv("LOCAL=", local);
135: free(local);
136:
137: if (pipe(err) < 0)
138: syslog(LOG_CRIT, "Cannot open pipe: %s\n", strerror(errno));
139: else
140: {
141: pid_t const pid = fork();
142: if (pid < 0)
143: syslog(LOG_CRIT, "Cannot fork: %s\n", strerror(errno));
144: else if (pid)
145: {
146: char buf[2048], *next = &buf[0];
147: char *const first = &buf[0], *const last = &buf[sizeof buf - 2];
148:
149: close(err[1]);
150: alarm(30);
151: last[1] = 0; // terminator on forced newline
152: while (signal_timed_out == 0 &&
153: signal_break == 0 &&
154: signal_child == 0)
155: {
156: int rd = read(err[0], next, last - next);
157: #if !defined NDEBUG
158: printf("rd=%2d, next=%2d\n", rd, next - first);
159: #endif
160: if (rd > 0)
161: {
162: char *p = first, *br;
163: next += rd;
164:
165: *next = next == last? '\n': 0; // force newline if full
166:
167: while ((br = strchr(p, '\n')) != NULL)
168: {
169: int level = LOG_CRIT;
170: *br = 0;
171:
172: /*
173: * e.g., "##This is a warning\n"
174: */
175: while (*p == '#' && level < LOG_DEBUG)
176: ++p, ++level;
177:
178: if (*p) syslog(level, "script: %s\n", p);
179: p = br + (br < last); // +1 if not forced newline
180: assert(first <= p && p <= next);
181: }
182:
183: memmove(first, p, next - p);
184: next -= p - first;
185: assert(first <= next && next < last);
186: }
187: else if (rd == 0 || errno != EINTR && errno != EAGAIN)
188: {
189: if (rd)
190: syslog(LOG_CRIT, "Pipe broken: %s\n", strerror(errno));
191: break;
192: }
193: }
194: alarm(0);
195: if (signal_timed_out || signal_break)
196: {
197: kill(pid, SIGTERM);
198: }
199: close(err[0]);
200:
201: for (;;)
202: {
203: int status;
204: pid_t wpid = wait(&status);
205: if (wpid < 0 && errno != EAGAIN && errno != EINTR)
206: {
207: syslog(LOG_CRIT,
208: "Cannot wait %s/" SCRIPTFILE "[%u]: %s\n",
209: home, (unsigned)wpid, strerror(errno));
210: break;
211: }
212: else if (wpid == pid)
213: {
214: if (WIFEXITED(status))
215: {
216: int level, s_rtc = WEXITSTATUS(status);
217: switch (s_rtc)
218: {
219: case 0: case 64: case 99: level = LOG_DEBUG; break;
220: default: level = LOG_CRIT; break;
221: }
222: rtc = s_rtc > 50;
223: syslog(level,
224: "%s/" SCRIPTFILE " with %s exited %d, rtc=%d\n",
225: home, ext, s_rtc, rtc);
226: }
227: else if (WIFSIGNALED(status))
228: {
229: syslog(LOG_CRIT,
230: "%s/" SCRIPTFILE " terminated with signal %d, rtc=%d\n",
231: home, WTERMSIG(status), rtc);
232: }
233: else continue; // stopped?
234:
235: break;
236: }
237: }
238: }
239: else // child process
240: {
241: close(0);
242: open("/dev/null", O_RDONLY);
243: close(1);
244: open("/dev/null", O_WRONLY);
245: close(2);
246: dup(err[1]);
247: close(err[0]);
248: close(err[1]);
249: closelog();
250: execv(SCRIPTFILE, argv);
251: syslog(LOG_MAIL|LOG_CRIT, "rcptfilter: cannot execv: %s\n",
252: strerror(errno));
253: exit(0);
254: }
255: }
256:
257: }
258: return rtc;
259: }
260:
261: int main(int argc, char *argv[])
262: {
263: int rtc = 0;
264: int i, uid = 0, gid = 0, err = 0;
265: char *ext = NULL, *home = getenv("HOME");
266: static char const argerror[] = "invoked with wrong argument: ";
267:
268: char *xargv[32];
269: size_t xargc = 0;
270:
271: openlog("rcptfilter", LOG_PID, LOG_MAIL);
272: xargv[xargc++] = SCRIPTFILE;
273: xargv[xargc++] = "RCPT";
274:
275: for (i = 1; i < argc; ++i)
276: {
277: char *a = argv[i];
278: int pass_it = 1;
279:
280: if (*a == '-')
281: {
282: pass_it = 0;
283: switch (*++a)
284: {
285: case 'D':
286: if (i + 1 >= argc)
287: {
288: syslog(LOG_CRIT, "%s-D requires a value\n", argerror);
289: ++err;
290: }
291: else
292: {
293: char *t = NULL;
294: a = argv[++i];
295: uid = strtoul(a, &t, 10);
296: if (t && *t == '/')
297: {
298: gid = strtoul(t + 1, &t, 10);
299: if (t && *t) t = NULL;
300: }
301: else t = NULL;
302: if (t == NULL)
303: {
304: syslog(LOG_CRIT, "%suidgid is %s\n", argerror, a);
305: ++err;
306: }
307: }
308: break;
309:
310: case 'M':
311: if (i + 1 >= argc)
312: {
313: syslog(LOG_CRIT, "%s-M requires a value\n", argerror);
314: ++err;
315: }
316: else if (strncmp(a = argv[++i],
317: "rcptfilter" , sizeof "rcptfilter" - 1) != 0)
318: {
319: syslog(LOG_CRIT, "%s-M with value %s\n", argerror, a);
320: ++err;
321: }
322: else
323: {
324: ext = strchr(a, '-');
325: if (ext)
326: ++ext;
327: else
328: ext = "";
329: }
330: break;
331:
332: case 'h':
333: puts(usage);
334: return 0;
335:
336: default:
337: pass_it = 1;
338: break;
339: }
340: }
341:
342: if (pass_it && xargc + 1 < sizeof xargv/ sizeof xargv[0])
343: {
344: xargv[xargc++] = a;
345: }
346: }
347:
348: xargv[xargc] = NULL;
349:
350: if (geteuid() == 0 && (uid || gid))
351: {
352: if (gid) setgid(gid);
353: setuid(uid);
354: }
355:
356: set_signal();
357: if (home && ext)
358: {
359: struct stat buf;
360: uid_t const me = geteuid();
361: gid_t const myg = getegid();
362:
363: if (chdir(home))
364: {
365: syslog(LOG_CRIT, "Cannot chdir to %s: %s\n",
366: home, strerror(errno));
367: }
368: else if (stat(SCRIPTFILE, &buf) != 0)
369: {
370: if (errno != ENOENT)
371: syslog(LOG_CRIT, "Cannot stat %s/" SCRIPTFILE ": %s\n",
372: home, strerror(errno));
373: }
374: else if (!S_ISREG(buf.st_mode) || !(
375: ((S_IXUSR & buf.st_mode) && (me == 0 || me == buf.st_uid)) ||
376: ((S_IXGRP & buf.st_mode) && (me == 0 || myg == buf.st_gid)) ||
377: (S_IXOTH & buf.st_mode)))
378: {
379: syslog(LOG_INFO, "%s/" SCRIPTFILE " not executable by %d/%d\n",
380: home, (int)me, (int)myg);
381: }
382: else
383: {
384: rtc = run_script(ext, xargv, home);
385: }
386: }
387: else
388: {
389: syslog(LOG_CRIT, "%smissing %s%s%s\n", argerror,
390: home ? "" : "HOME env variable",
391: home || ext ? "" : " and ",
392: ext ? "" : "-M argument");
393: }
394:
395: return rtc;
396: }
License