Lots of stuff. 1.1.0
authormdw <mdw>
Thu, 9 Oct 2003 15:05:34 +0000 (15:05 +0000)
committermdw <mdw>
Thu, 9 Oct 2003 15:05:34 +0000 (15:05 +0000)
20 files changed:
.cvsignore
Makefile
cdb-assign.1 [new file with mode: 0644]
cdb-check-domain.1 [new file with mode: 0644]
cdb-list.1 [new file with mode: 0644]
cdb-probe.1 [new file with mode: 0644]
check-sender [new file with mode: 0755]
check-sender.1 [new file with mode: 0644]
gorp.1 [new file with mode: 0644]
gorp.c [new file with mode: 0644]
if-mtu.1 [new file with mode: 0644]
if-mtu.c [new file with mode: 0644]
locking.1 [new file with mode: 0644]
locking.c [new file with mode: 0644]
not.1 [new file with mode: 0644]
qmail-checkspam.8 [new file with mode: 0644]
splitconf
splitconf.1 [new file with mode: 0644]
splitconf.test [new file with mode: 0644]
unfwd.1 [new file with mode: 0644]

index 3910527..c66fc9d 100644 (file)
@@ -1 +1,2 @@
 cdb-check-domain cdb-probe not qmail-checkspam
+gorp if-mtu locking
index 841e7fd..e50424d 100644 (file)
--- 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 (file)
index 0000000..17613ba
--- /dev/null
@@ -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, <mdw@nsict.org>
diff --git a/cdb-check-domain.1 b/cdb-check-domain.1
new file mode 100644 (file)
index 0000000..10682e7
--- /dev/null
@@ -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, <mdw@nisict.org>
diff --git a/cdb-list.1 b/cdb-list.1
new file mode 100644 (file)
index 0000000..e152c62
--- /dev/null
@@ -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, <mdw@nsict.org>
diff --git a/cdb-probe.1 b/cdb-probe.1
new file mode 100644 (file)
index 0000000..c2858b2
--- /dev/null
@@ -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, <mdw@nisict.org>
diff --git a/check-sender b/check-sender
new file mode 100755 (executable)
index 0000000..378f2dc
--- /dev/null
@@ -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 (file)
index 0000000..e8c8ace
--- /dev/null
@@ -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, <mdw@nisict.org>
diff --git a/gorp.1 b/gorp.1
new file mode 100644 (file)
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 <mdw@nsict.org>
diff --git a/gorp.c b/gorp.c
new file mode 100644 (file)
index 0000000..933460c
--- /dev/null
+++ b/gorp.c
@@ -0,0 +1,40 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <mLib/alloc.h>
+#include <mLib/base64.h>
+#include <mLib/dstr.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+
+#include <catacomb/rand.h>
+#include <catacomb/noise.h>
+
+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 (file)
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, <mdw@nsict.org>
diff --git a/if-mtu.c b/if-mtu.c
new file mode 100644 (file)
index 0000000..b4ddbee
--- /dev/null
+++ b/if-mtu.c
@@ -0,0 +1,36 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+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 (file)
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, <mdw@nsict.org>
diff --git a/locking.c b/locking.c
new file mode 100644 (file)
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 <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+
+/*----- 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 (file)
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, <mdw@nsict.org>
diff --git a/qmail-checkspam.8 b/qmail-checkspam.8
new file mode 100644 (file)
index 0000000..0ecebef
--- /dev/null
@@ -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, <mdw@nsict.org>
+
index 4a09f0a..80c4e43 100755 (executable)
--- 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 (file)
index 0000000..e8b4016
--- /dev/null
@@ -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, <mdw@nsict.org>
diff --git a/splitconf.test b/splitconf.test
new file mode 100644 (file)
index 0000000..519f0bf
--- /dev/null
@@ -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 (file)
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 <mdw@nsict.org>
+