From f342fce2e614f9d34503082b165bf70c0a211631 Mon Sep 17 00:00:00 2001 From: mdw Date: Thu, 9 Oct 2003 15:05:34 +0000 Subject: [PATCH] Lots of stuff. --- .cvsignore | 1 + Makefile | 53 +++++++++- cdb-assign.1 | 31 ++++++ cdb-check-domain.1 | 23 +++++ cdb-list.1 | 29 ++++++ cdb-probe.1 | 19 ++++ check-sender | 3 + check-sender.1 | 23 +++++ gorp.1 | 16 +++ gorp.c | 40 ++++++++ if-mtu.1 | 20 ++++ if-mtu.c | 36 +++++++ locking.1 | 107 +++++++++++++++++++++ locking.c | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++++ not.1 | 30 ++++++ qmail-checkspam.8 | 66 +++++++++++++ splitconf | 139 +++++++++++++++++++++------ splitconf.1 | 115 ++++++++++++++++++++++ splitconf.test | 40 ++++++++ unfwd.1 | 38 ++++++++ 20 files changed, 1071 insertions(+), 36 deletions(-) create mode 100644 cdb-assign.1 create mode 100644 cdb-check-domain.1 create mode 100644 cdb-list.1 create mode 100644 cdb-probe.1 create mode 100755 check-sender create mode 100644 check-sender.1 create mode 100644 gorp.1 create mode 100644 gorp.c create mode 100644 if-mtu.1 create mode 100644 if-mtu.c create mode 100644 locking.1 create mode 100644 locking.c create mode 100644 not.1 create mode 100644 qmail-checkspam.8 create mode 100644 splitconf.1 create mode 100644 splitconf.test create mode 100644 unfwd.1 diff --git a/.cvsignore b/.cvsignore index 3910527..c66fc9d 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1 +1,2 @@ cdb-check-domain cdb-probe not qmail-checkspam +gorp if-mtu locking diff --git a/Makefile b/Makefile index 841e7fd..e50424d 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,53 @@ ## ## No proper build system here. Just kludgy hacks. +VERSION = 1.1.0 + +SCRIPTS = \ + cdb-assign cdb-list \ + unfwd splitconf + PROGS = \ qmail-checkspam not \ cdb-probe cdb-check-domain \ + gorp \ + locking if-mtu + +OTHERS = \ xtitle.so +MAN1 = \ + not.1 \ + cdb-assign.1 cdb-list.1 cdb-probe.1 cdb-check-domain.1 \ + gorp.1 \ + unfwd.1 splitconf.1 locking.1 if-mtu.1 + +MAN8 = \ + qmail-checkspam.8 + CC = gcc LD = gcc -CFLAGS = -O2 -g -pedantic -Wall +CFLAGS = -O2 -g -pedantic -Wall -DVERSION=\"$(VERSION)\" LINK = $(LD) $(LDFLAGS) -o $@ $^ -all: $(PROGS) +prefix = /usr/local +bindir = $(prefix)/bin +mandir = $(prefix)/man +man1dir = $(mandir)/man1 +man8dir = $(mandir)/man8 +libdir = $(prefix)/lib + +all: $(PROGS) $(OTHERS) + +install: all + [ -d $(bindir) ] || install -d $(bindir) + install -m775 $(PROGS) $(SCRIPTS) $(bindir) + [ -d $(man1dir) ] || install -d $(man1dir) + install -m644 $(MAN1) $(man1dir) + [ -d $(man8dir) ] || install -d $(man8dir) + install -m644 $(MAN8) $(man8dir) + [ -d $(libdir) ] || install -d $(libdir) + install -m644 xtitle.so $(libdir) qmail-checkspam: qmail-checkspam.o $(LINK) -lspamc @@ -31,6 +67,15 @@ xtitle.o: xtitle.c xtitle.so: xtitle.o $(LINK) -shared -clean:; rm -f *.o $(PROGS) +gorp: gorp.o + $(LINK) -lcatacomb -lmLib + +locking: locking.o + $(LINK) -lmLib + +if-mtu: if-mtu.o + $(LINK) + +clean:; rm -f *.o *~ $(PROGS) $(OTHERS) -.PHONY: all clean +.PHONY: all clean install diff --git a/cdb-assign.1 b/cdb-assign.1 new file mode 100644 index 0000000..17613ba --- /dev/null +++ b/cdb-assign.1 @@ -0,0 +1,31 @@ +.\" -*-nroff-*- +.TH cdb-assign 1 "9 October 2003" "Straylight/Edgeware" +.SH SYNOPSIS +.B cdb-assign +.I cdb +.RI [ input ...] +.SH DESCRIPTION +Constructs a CDB file (see +.BR cdbmake (1), +.BR cdbget (1)) +from the assignments in the +.I input +files (or stdin if there are none). +.PP +Comments and blank lines in the inputs are ignored; other lines must +have the form +.RI ` key +.B = +.IR value '. Leading and trailing spaces in each are stripped. This is +a convenient tool for building simple CBD files, rather than +comprehensive one. +.PP +The CDB file is written to +.IB cdb .new +and renamed to +.I cdb +when it's finished. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/cdb-check-domain.1 b/cdb-check-domain.1 new file mode 100644 index 0000000..10682e7 --- /dev/null +++ b/cdb-check-domain.1 @@ -0,0 +1,23 @@ +.\" -*-nroff-*- +.TH cdb-check-domain 1 "10 October 2003" "Straylight/Edgeware" +.SH NAME +cdb-check-domain \- check if a domain name exists in a CDB file +.SH SYNOPSIS +.B cdb-check-domain +.I dom +.BI < cdb +.SH DESCRIPTION +If the key +.IR dom , +or any key +.BI . suff +which is a right substring of +.I dom +exists in the CDB file +.I cdb +then exit 0; if it doesn't exist, exit 1. If anything bad happened, +then exit 111. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/cdb-list.1 b/cdb-list.1 new file mode 100644 index 0000000..e152c62 --- /dev/null +++ b/cdb-list.1 @@ -0,0 +1,29 @@ +.\" -*-nroff-*- +.TH cdb-list 1 "9 October 2003" "Straylight/Edgeware" +.SH SYNOPSIS +.B cdb-list +.I cdb +.RI [ input ...] +.SH DESCRIPTION +Constructs a CDB file (see +.BR cdbmake (1), +.BR cdbget (1)) +from a list of items in the +.I input +files (or stdin if there are none). +.PP +Comments and blank lines in the inputs are ignored. Each other line +.I line +causes an entry to be inserted into the BDB, whose key is +.I line +with leading and trailing spaces stripped, and whose value is empty. +.PP +The CDB file is written to +.IB cdb .new +and renamed to +.I cdb +when it's finished. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/cdb-probe.1 b/cdb-probe.1 new file mode 100644 index 0000000..c2858b2 --- /dev/null +++ b/cdb-probe.1 @@ -0,0 +1,19 @@ +.\" -*-nroff-*- +.TH cdb-probe 1 "10 October 2003" "Straylight/Edgeware" +.SH NAME +cdb-probe \- check if a key exists in a CDB file +.SH SYNOPSIS +.B cdb-probe +.I key +.BI < cdb +.SH DESCRIPTION +If the key +.I key +exists in the CDB file +.I cdb +then exit 0; if it doesn't exist, exit 1. If anything bad happened, +then exit 111. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/check-sender b/check-sender new file mode 100755 index 0000000..378f2dc --- /dev/null +++ b/check-sender @@ -0,0 +1,3 @@ +#! /bin/sh +bouncesaying "fatal: sending to this address forbidden" \ + not cdb-check-domain "${SENDER##*@}" diff --git a/check-sender.1 b/check-sender.1 new file mode 100644 index 0000000..e8c8ace --- /dev/null +++ b/check-sender.1 @@ -0,0 +1,23 @@ +.\" -*-nroff-*- +.TH check-sender 1 "9 October 2003" "Straylight/Edgeware" +.SH NAME +check-sender \- check an envelope sender's domain in a CDB file +.SH SYNOPSIS +(In a +.B .qmail +file) +.br +.B |check-sender +.BI < cdb +.SH DESCRIPTION +If the domain part of the email address in the +.B SENDER +environment variable is matched in +.I cdb +according to +.BR cdb-check-domain (1) +then exit 0. Otherwise, bounce the message with a refusal message. +.SH BUGS +None known +.SH AUTHOR +Mark Wooding, diff --git a/gorp.1 b/gorp.1 new file mode 100644 index 0000000..f00ce13 --- /dev/null +++ b/gorp.1 @@ -0,0 +1,16 @@ +.\" -*-nroff-*- +.TH gorp 1 "9 October 2003" "Straylight/Edgeware" +.SH NAME +gorp \- write a short random string +.SH SYNOPSIS +.B gorp +.RI [ bits ] +.SH DESCRIPTION +Generates +.I bits +random bits (must be a multiple of 8, default is 128) and writes the +resulting string to standard output in base64 format. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding diff --git a/gorp.c b/gorp.c new file mode 100644 index 0000000..933460c --- /dev/null +++ b/gorp.c @@ -0,0 +1,40 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +int main(int argc, char *argv[]) +{ + dstr d = DSTR_INIT; + base64_ctx b; + char *p; + size_t n; + + ego(argv[0]); + if (argc > 2) { + pquis(stderr, "Usage: $ [BITS]\n"); + exit(EXIT_FAILURE); + } + n = argc == 2 ? atoi(argv[1]) : 128; + if (!n || n % 8) die(EXIT_FAILURE, "bad bit count"); + n >>= 3; + p = xmalloc(n); + rand_noisesrc(RAND_GLOBAL, &noise_source); + rand_seed(RAND_GLOBAL, 160); + rand_get(RAND_GLOBAL, p, n); + base64_init(&b); + b.maxline = 0; + b.indent = ""; + base64_encode(&b, p, n, &d); + base64_encode(&b, 0, 0, &d); + printf("%s\n", d.buf); + return (0); +} diff --git a/if-mtu.1 b/if-mtu.1 new file mode 100644 index 0000000..c10e82f --- /dev/null +++ b/if-mtu.1 @@ -0,0 +1,20 @@ +.TH "if-mtu" 1 "24 September 2003" "Mark Wooding" "Toys" +.SH NAME +if-mtu \- find MTU of an interface +. +.SH SYNOPSIS +.B if\-mtu +.I iface +. +.SH "DESCRIPTION" +The +.B if\-mtu +prints the MTU of the network interface +.I iface +to standard output, and exits. +. +.SH "BUGS" +None. +. +.SH "AUTHOR" +Mark Wooding, diff --git a/if-mtu.c b/if-mtu.c new file mode 100644 index 0000000..b4ddbee --- /dev/null +++ b/if-mtu.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + int fd; + struct ifreq ifr; + if (argc != 2) { + fprintf(stderr, "usage: %s IFACE\n", argv[0]); + exit(1); + } + if (strlen(argv[1]) >= sizeof(ifr.ifr_name)) { + fprintf(stderr, "%s: interface name `%s' too long\n", argv[0], argv[1]); + exit(1); + } + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "%s: socket: %s\n", argv[0], strerror(errno)); + exit(1); + } + strcpy(ifr.ifr_name, argv[1]); + if (ioctl(fd, SIOCGIFMTU, &ifr)) { + fprintf(stderr, "%s: ioctl(SIOCGIFMTU): %s\n", argv[0], strerror(errno)); + exit(1); + } + printf("%d\n", ifr.ifr_mtu); + return (0); +} diff --git a/locking.1 b/locking.1 new file mode 100644 index 0000000..04842e7 --- /dev/null +++ b/locking.1 @@ -0,0 +1,107 @@ +.TH "locking" 1 "11 May 2003" "Mark Wooding" "Toys" +.SH NAME +locking \- run a program with a lock held +. +.SH SYNOPSIS +.B locking +.RB [ \-cfwx ] +.RB [ \-p +.IR realprog ] +.RB [ \-t +.IR time ] +.I file +.I prog +.IR args ... +. +.SH "DESCRIPTION" +The +.B locking +program opens and locks +.I file +while it runs some program +.IR prog . +This is handy in shell scripts and (particularly) crontab entries. It +was initially written to ensure that news fetches only occurred if the +previous one had completed. +.PP +The program uses +.BR fcntl (2)-style +locking. It will not leave stale locks lying around: the lock is +released if +.I prog +or the +.B locking +program itself quits for any reason. +.PP +The following options are supported: +.TP +.B "\-h, \-\-help" +Write a full help message to stdout, and exit zero. +.TP +.B "\-v, \-\-version" +Write the version number to stdout, and exit zero. +.TP +.B "\-u, \-\-usage" +Write a brief usage message to stdout, and exit zero. +.TP +.BR "\-c, \-\-" [ "no\-" ] "create" +Create +.I file +if it doesn't already exist. The file is created with mode 666 as +modified by the process's umask in the usual way. This is the default +behaviour. The options +.B +c +or +.B \-\-no\-create +will cause +.B locking +to fail if the file does not already exist. +.TP +.BR "\-f, \-\-" [ "no\-" ] "fail" +Fail (report an error and exit nonzero) if +.I file +is already locked. The default behaviour is to exit successfully +without running +.IR prog . +.TP +.BR "\-w, \-\-" [ "no\-" ] "wait" +Wait for the lock on +.I file +to become available. The default behaviour is to give up immediately. +.TP +.BR "\-x, \-\-" [ "no\-" ] "exclusive" +Obtain an exclusive lock on the file. This is the default. The options +.B +x +or +.B \-\-no\-exclusive +obtain a non-exclusive lock on the file instead. +.TP +.BI "\-p, \-\-program=" realprog +Run the program +.IR realprog , +passing it +.I prog +as its first argument. The default behaviour is to run +.IR prog . +.TP +.BI "\-t, \-\-timeout=" time +Wait for +.I time +(an integer, optionally followed by +.RB ` d ', +.RB ` h ', +.RB ` m ', +or +.RB ` s ' +for days, hours, minutes or seconds, respectively) for the lock to +become available, and then give up. This only makes sense with the +.B \-\-wait +option, so that is turned on automatically. +.SH "BUGS" +The +.B locking +program messes with alarms. It tries to put them back the way it found +them, but may get things wrong. +. +.SH "AUTHOR" +Mark Wooding, diff --git a/locking.c b/locking.c new file mode 100644 index 0000000..735ba47 --- /dev/null +++ b/locking.c @@ -0,0 +1,278 @@ +/* -*-c-*- + * + * $Id: locking.c,v 1.1 2003/10/09 15:05:34 mdw Exp $ + * + * Lock a file, run a program + * + * (c) 2003 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the Toys utilties collection. + * + * Toys is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Toys is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Toys; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: locking.c,v $ + * Revision 1.1 2003/10/09 15:05:34 mdw + * Lots of stuff. + * + * Revision 1.3 2003/09/24 14:58:08 mdw + * Fix options parsing again. + * + * Revision 1.2 2003/09/24 14:14:03 mdw + * Fix option handling behaviour. + * + * Revision 1.1 2003/05/11 13:30:04 mdw + * Initial checkin. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*----- Static variables --------------------------------------------------*/ + +static jmp_buf jmp; + +/*----- Main code ---------------------------------------------------------*/ + +static void alrm(int s) { longjmp(jmp, 1); } + +static void usage(FILE *fp) +{ + pquis(fp, + "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n"); +} + +static void version(FILE *fp) +{ + pquis(fp, "$ (toys, version " VERSION "\n"); +} + +static void help(FILE *fp) +{ + version(fp); + putchar('\n'); + usage(fp); + pquis(fp, "\n\ +Lock FILE and run PROG, passing ARGS. Options are:\n\ +\n\ +-h, --help Show this help message.\n\ +-v, --version Show version string.\n\ +-u, --usage Show terse usage summary.\n\ +\n\ +-c, --[no-]create Create FILE if it doesn't exist [default: on].\n\ +-f, --[no-]fail Fail if the file is already locked [default: off].\n\ +-w, --[no-]wait Wait for the lock to be available [default: off].\n\ +-x, --[no-]exclusive Get an exclusive (writer) lock [default: on].\n\ +-p, --program=REALPROG Run REALPROG instead of PROG.\n\ +-t, --timeout=TIME Wait for TIME for lock to become available.\n\ +"); +} + +int main(int argc, char *argv[]) +{ + const char *file = 0; + const char *prog = 0; + char *const *av; + void (*oalrm)(int) = 0; + int fd; + struct flock l; + char *p; + int t = -1; + unsigned int ot; + time_t nt; + pid_t kid; + int st; + +#define f_bogus 1u +#define f_wait 2u +#define f_fail 4u +#define f_create 8u +#define f_excl 16u + + unsigned f = f_create | f_excl; + + ego(argv[0]); + + for (;;) { + static const struct option opts[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + { "wait", OPTF_NEGATE, 0, 'w' }, + { "fail", OPTF_NEGATE, 0, 'f' }, + { "create", OPTF_NEGATE, 0, 'c' }, + { "program", OPTF_ARGREQ, 0, 'p' }, + { "timeout", OPTF_ARGREQ, 0, 't' }, + { "exclusive", OPTF_NEGATE, 0, 'x' }, + { 0, 0, 0, 0 } + }; + + int i = mdwopt(argc, argv, "-hvuw+f+c+x+p:t:", opts, + 0, 0, OPTF_NEGATION); + if (i < 0) + break; + switch (i) { + case 'h': + help(stdout); + exit(0); + case 'v': + version(stdout); + exit(0); + case 'u': + usage(stdout); + exit(0); + case 'w': + f |= f_wait; + break; + case 'w' | OPTF_NEGATED: + f &= ~f_wait; + break; + case 'f': + f |= f_fail; + break; + case 'f' | OPTF_NEGATED: + f &= ~f_fail; + break; + case 'c': + f |= f_create; + break; + case 'c' | OPTF_NEGATED: + f &= ~f_create; + break; + case 'x': + f |= f_excl; + break; + case 'x' | OPTF_NEGATED: + f &= ~f_excl; + break; + case 't': + errno = 0; + t = strtol(optarg, &p, 0); + switch (*p) { + case 'd': t *= 24; + case 'h': t *= 60; + case 'm': t *= 60; + case 's': p++; + case 0: break; + default: die(111, "unknown time unit `%c'", *p); + } + if (*p || t < 0 || errno) + die(111, "bad time value `%s'", optarg); + f |= f_wait; + break; + case 'p': + prog = optarg; + break; + case 0: + if (file) { + optind--; + goto doneopts; + } + file = optarg; + break; + default: + f |= f_bogus; + break; + } + } + +doneopts: + if (f & f_bogus || argc - optind < 1) { + usage(stderr); + exit(EXIT_FAILURE); + } + + av = &argv[optind]; + if (!prog) + prog = av[0]; + if ((fd = open(file, + ((f & f_create ? O_CREAT : 0) | + (f & f_excl ? O_RDWR : O_RDONLY)), 0666)) < 0) + die(111, "error opening `%s': %s", file, strerror(errno)); + l.l_type = f & f_excl ? F_WRLCK : F_RDLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + nt = time(0); + if (setjmp(jmp)) { + errno = EAGAIN; + nt = t; + } else { + ot = alarm(0); + oalrm = signal(SIGALRM, alrm); + if (t >= 0) + alarm(t); + if (fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0) + errno = 0; + } + signal(SIGALRM, oalrm); + if (ot) { + nt = time(0) - nt; + if (nt > ot) + raise(SIGALRM); + else + alarm(ot - nt); + } + if (errno && + ((errno != EAGAIN && errno != EWOULDBLOCK && errno != EACCES) || + (f & f_fail))) + die(111, "error locking `%s': %s", file, strerror(errno)); + if (errno) + exit(0); + + if ((kid = fork()) < 0) + die(111, "error from fork: %s", strerror(errno)); + if (!kid) { + close(fd); + execvp(prog, av); + die(111, "couldn't exec `%s': %s", prog, strerror(errno)); + } + if (waitpid(kid, &st, 0) < 0) + die(EXIT_FAILURE, "error from wait: %s", strerror(errno)); + l.l_type = F_UNLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + fcntl(fd, F_SETLK, &l); + close(fd); + if (WIFEXITED(st)) + exit(WEXITSTATUS(st)); + else + exit(255); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/not.1 b/not.1 new file mode 100644 index 0000000..c71e6d1 --- /dev/null +++ b/not.1 @@ -0,0 +1,30 @@ +.\" -*-nroff-*- +.TH not 1 "9 October 2003" "Straylight/Edgeware" +.SH NAME +not \- invert the sense of a program +.SH SYNOPSIS +.B not +.I program +.IR args ... +.SH DESCRIPTION +Runs +.IR program , +passing it +.IR args . +If +.I program +exits 111, or there's some kind of problem, then +.B not +exits 111; if +.I program +exits 0 then +.B not +exits 100; otherwise +.B not +exits 0. This is useful in +.BR bouncesaying (1) +lines. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/qmail-checkspam.8 b/qmail-checkspam.8 new file mode 100644 index 0000000..0ecebef --- /dev/null +++ b/qmail-checkspam.8 @@ -0,0 +1,66 @@ +.\" -*-nroff-*- +.TH qmail-checkspam 8 "9 October 2003" "Straylight/Edgeware" +.SH NAME +qmail-checkspam \- check messages for spam before queueing them +.SH SYNOPSIS +.B qmail-checkspam +.SH DESCRIPTION +The +.B qmail-checkspam +program provides the same interface as +.BR qmail-queue (8). +It reads a message on file descriptor 0, and passes it to +.BR spamd (8). +If +.B spamd +thinks the message is spam, then +.B qmail-checkspam +exits 31, indicating a permanent refusal. +Otherwise, +.B qmail-checkspam +feeds the message, augmented by SpamAssassin's +.B X-Spam-Status +header, to +.BR qmail-queue (8). +.PP +You will usually run +.B qmail-checkspam +by applying the +.B QMAILQUEUE +patch to +.B qmail (7) +and setting the +.B QMAILQUEUE +environment variable to refer to +.B qmail-checkspam +in, e.g., the +.B /etc/hosts.deny +file (see +.BR hosts_options (5)). +.SH ENVIRONMENT +.TP +.B QMAIL_CHECKSPAM_SPAMDHOST +The hostname or IP address of the +.B spamd +to connect to. Default is +.BR localhost . +.TP +.B QMAIL_CHECKSPAM_SPAMDPORT +The port number to connect to. The default is +.BR 783 . +.TP +.B QMAIL_CHECKSPAM_THRESH +The threshold score for rejection. The default is whatever +.B spamd +is configured to. +.TP +.B QMAIL_CHECKSPAM_QUEUE +The +.BR qmail-queue -compatible +program to pass good mail to. The default is +.BR /var/qmail/bin/qmail-queue . +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, + diff --git a/splitconf b/splitconf index 4a09f0a..80c4e43 100755 --- a/splitconf +++ b/splitconf @@ -1,11 +1,21 @@ #! /usr/bin/tclsh +#----- Miscellaneous utilities ---------------------------------------------- + +# die MSG +# +# Something didn't work. Exit right now. + proc die {msg} { global argv0 puts stderr "$argv0: $msg" exit 1 } +# usage FILE +# +# Write a usage message to FILE, which is a file handle. + proc usage {file} { global argv0 puts $file "Usage: \n\ @@ -13,25 +23,9 @@ proc usage {file} { $argv0 -u OUTPUT FILE FILE ..." } -set job "split" -while {[llength $argv]} { - switch -glob -- [lindex $argv 0] { - "-u" - "--unsplit" { - set job "unsplit" - if {[llength $argv] < 2} { die "option `-u' needs an argument" } - set output [lindex $argv 1] - set argv [lrange $argv 1 end] - } - "-d" - "--delete" { set job "delete" } - "-s" - "--split" { set job "split" } - "-h" - "--help" { usage stdout; exit 0 } - "-" { break } - "--" { set argv [lrange $argv 1 end]; break } - "-*" { die "unknown option `[lindex $argv 0]'"; exit 1 } - default { break } - } - set argv [lrange $argv 1 end] -} +# clear-arrays ARRAY ... +# +# Make each named ARRAY exist and be empty. proc clear-arrays {args} { foreach i $args { @@ -41,6 +35,14 @@ proc clear-arrays {args} { } } +#------ Write-safe ---------------------------------------------------------- + +# write-safe STUFF [TIDY] +# +# Do some safe I/O. If STUFF succeeds, do TIDY and commit the modifications; +# otherwise, do TIDY and back out all the changes. See also write-safe-open, +# write-safe-file and write-safe-delete. + proc write-safe {stuff {tidy {}}} { global _ws_close _ws_del _ws_new clear-arrays _ws_del _ws_new @@ -80,6 +82,13 @@ proc write-safe {stuff {tidy {}}} { return {} } +# write-safe-open NAME [TRANS] +# +# Open file NAME for writing, with the translation mode TRANS (default is +# `auto'); return the file handle. The file NAME is not destroyed until the +# changes are committed by an enclosing write-safe completing. You can close +# the file handle if you like; write-safe will close it automatically anyway. + proc write-safe-open {name {trans auto}} { global _ws_close _ws_new if {[file isdirectory $name]} { error "`$name' is a directory" } @@ -90,17 +99,33 @@ proc write-safe-open {name {trans auto}} { return $f } +# write-safe-delete NAME +# +# Delete file NAME. The file isn't actually removed until the enclosing +# write-safe completes. + proc write-safe-delete {name} { global _ws_del set _ws_del($name) 0 } +# write-safe-file NAME CONTENTS [TRANS] +# +# Write CONTENTS to FILE, using translation mode TRANS (default `auto'). The +# file isn't actually replaced until the changes are committed by an +# enclosing write-safe completing. + proc write-safe-file {name contents {trans auto}} { set f [write-safe-open $name $trans] puts -nonewline $f $contents close $f } +# read-file NAME [TRANS] +# +# Evaluates to the contents of the file NAME under translation mode TRANS +# (default `auto'). + proc read-file {name {trans auto}} { set f [open $name] fconfigure $f -translation $trans @@ -109,12 +134,23 @@ proc read-file {name {trans auto}} { return $c } +#----- Splitconf-specific stuff --------------------------------------------- + +# write-safe-manifest F L +# +# Writes the list of filenames L to the manifest file associated with config +# file F. + proc write-safe-manifest {f l} { set f [write-safe-open $f.files] foreach i $l { puts $f $i } close $f } +# old-files CONF +# +# Returns the filenames in the current manifest of the config file CONF. + proc old-files {conf} { set old {} if {[file exists $conf.files]} { @@ -125,6 +161,28 @@ proc old-files {conf} { return $old } +#----- Main code ------------------------------------------------------------ + +set job "split" +while {[llength $argv]} { + switch -glob -- [lindex $argv 0] { + "-u" - "--unsplit" { + set job "unsplit" + if {[llength $argv] < 2} { die "option `-u' needs an argument" } + set output [lindex $argv 1] + set argv [lrange $argv 1 end] + } + "-d" - "--delete" { set job "delete" } + "-s" - "--split" { set job "split" } + "-h" - "--help" { usage stdout; exit 0 } + "-" { break } + "--" { set argv [lrange $argv 1 end]; break } + "-*" { die "unknown option `[lindex $argv 0]'"; exit 1 } + default { break } + } + set argv [lrange $argv 1 end] +} + set rc 0 clear-arrays opt array set opt { @@ -165,44 +223,59 @@ switch $job { set old [old-files $conf] set c [open $conf r] catch { unset o } - set file "" set spill "" + set donebefore 0 array set new {} write-safe { while {[gets $c line] >= 0} { if {[regexp -- {^\[(.*)\]\s*$} $line . name]} { if {[info exists o]} { - puts -nonewline $o $file close $o + } elseif {!$donebefore} { + exec "sh" "-c" $opt(before) <@ stdin >@ stdout 2>@ stderr + set donebefore 1 + } + if {[string equal $name ""]} { + catch { unset o } } else { - exec "sh" "-c" $opt(before) + set name "$opt(prefix)$name" + set o [write-safe-open $name] + set new($name) 1 + set spill "" } - set name "$opt(prefix)$name" - set o [write-safe-open $name] - set new($name) 1 - set file "" - set spill "" } elseif {[info exists o]} { switch -regexp -- $line { {^\s*$} { append spill "$line\n" } {^\#\#} { } - {^\!} { append file "$spill[string range $line 1 end]\n" } - default { append file "$spill$line\n" } + {^\!} { + puts -nonewline $o "$spill[string range $line 1 end]\n" + set spill "" + } + default { puts -nonewline $o "$spill$line\n"; set spill "" } } } elseif {[regexp -- {^\s*(\#|$)} $line]} { continue - } elseif {[regexp -- {^\s*([-\w]+)\s*=\s*(.*\S|)\s*$} $line . k v]} { + } elseif {[regexp -- \ + {^\s*([-./\w]+)\s*=\s*(.*\S|)\s*$} $line . k v]} { if {![info exists opt($k)]} { error "unknown configuration option `$k'" } else { set opt($k) $v } + } elseif {[regexp -- \ + {^\s*([-./\w]+)\s*:\s*(.*\S|)\s*$} $line . name d]} { + if {!$donebefore} { + exec "sh" "-c" $opt(before) <@ stdin >@ stdout 2>@ stderr + set donebefore 1 + } + set name "$opt(prefix)$name" + set new($name) 1 + write-safe-file $name "$d\n" } else { error "unknown preamble directive" } } if {[info exists o]} { - puts -nonewline $o $file close $o } close $c @@ -211,7 +284,9 @@ switch $job { } write-safe-manifest $conf [array names new] } { - exec "sh" "-c" $opt(after) + exec "sh" "-c" $opt(after) <@ stdin >@ stdout 2>@ stderr } } } + +#----- That's all, folks ---------------------------------------------------- diff --git a/splitconf.1 b/splitconf.1 new file mode 100644 index 0000000..e8b4016 --- /dev/null +++ b/splitconf.1 @@ -0,0 +1,115 @@ +.\" -*-nroff-*- +.de hP +.IP +.ft B +\h'-\w'\\$1\ 'u'\\$1\ \c +.ft P +.. +.ie t .ds o \(bu +.el .ds o o +. +.TH splitconf 1 "9 October 2003" "Straylight/Edgeware" +.SH NAME +splitconf \- break a single configuration file out into small chunks +. +.SH SYNOPSIS +.B splitconf +.B \-u +.IR config +.I file +... +.br +.B splitconf +.B \-d +.I config +.br +.B splitconf +.RB [ \-s \] +.I config +. +.SH DESCRIPTION +The +.B splitconf +program breaks a configuration file into lots of small files, and +remembers what they are. It is designed to be +.IR safe : +if writing any of the components fails, none of the files is changed. +Alas, the update is not quite atomic -- doing that involves messing with +symbolic links and is just too much like hard work. +.PP +The command-line arguments are as follows: +.TP +.B \-h, \-\-help +Print a very short help message to standard output and exit. +.TP +.B \-u, \-\-unsplit +Gather a list of +.IR file s +into a single +.BR splitconf -managed +configuration file +.IR config . +.TP +.B \-d, \-\-delete +Delete the files split out from +.IR config . +.TP +.B \-s, \-\-split +Split files out from +.IR config . +(This is the default.) +. +.SH SPLITCONF FILES +The input files to +.B splitconf +start with a +.I preamble +section, followed by a number of +.I file +sections. +.PP +The preamble section may contain: +.hP \*o +blank lines, which are ignored; +.hP \*o +meta-comments, which begin with a +.RB ` ## ' +pair, and are also ignored; +.hP \*o +option settings, of the form +.RI ` option +.B = +.IR value ' +(see below); and +.hP \*o +one-liner file contents descriptions, of the form +.RI ` name \c +.B : +.IR contents '. +.PP +A file section begins with a line of the form +.RB ` [ \c +.IR name \c +.BR ] ' +and is followed by lines to write to the file. In these lines, comments +(lines beginning with a +.RB ` # ' +are stripped out, and any trailing blank lines are removed. Also, any +line beginning with an exclamation mark +.RB ` ! ' +is written out with the leading exclamation mark removed. +.PP +Options are as follows. +.TP +.B prefix +A string to attach to the front of all filenames. (Default is empty.) +.TP +.B before +A shell command to execute before starting to write any files. +.TP +.B after +A shell command to execute after committing the changes or backing out. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding, diff --git a/splitconf.test b/splitconf.test new file mode 100644 index 0000000..519f0bf --- /dev/null +++ b/splitconf.test @@ -0,0 +1,40 @@ +## Splitconf test script + +before = echo "Before!" +after = echo "After!" +prefix = test- + +one: unus +two: duo +three: tres + +[four] +quatuor + +[] +before = echo "Buggy!" + +[five] +cinque + +[comments] +nothing here +## but us chickens +who said that? +!## nobody, in a literal comment +blank line follows + +and then some more text +# and +then just one more +blank line +! + +[] +prefix = + +test.strange: one-liner + +[test.odd] +just some more lines +like this diff --git a/unfwd.1 b/unfwd.1 new file mode 100644 index 0000000..7a88f05 --- /dev/null +++ b/unfwd.1 @@ -0,0 +1,38 @@ +.\" -*-nroff-*- +.TH unfwd 1 "9 October 2003" "Straylight/Edgeware" +.SH NAME +unfwd \- extract a MIME-formatted forwarded mesage +.SH SYNOPSIS +.B unfwd +.I program +.IR args ... +.SH DESCRIPTION +Reads a MIME-formatted email message from standard input. For each part +of content-type +.BR message/822 , +run +.I program +and feed the enclosed message to it on its standard input. For each +part of content-type +.BR multipart/digest , +pick the digest apart and feed each message in turn to +.I program +on standard input. +.PP +If all went well, +.B unfwd +exits 0. If +.I program +exits nonzero, or the original message didn't contain any MIME forwarded +messages, then a message is printed to standard error and +.B unfwd +exits 100, which causes +.B qmail-local +to bounce the mail. If something else went wrong, +.B unfwd +returns 111, indicating a temporary failure. +.SH BUGS +None known. +.SH AUTHOR +Mark Wooding + -- 2.11.0