summaryrefslogtreecommitdiff
path: root/usr.sbin/ctm/ctm_smail
diff options
context:
space:
mode:
authorThomas Graichen <graichen@cvs.openbsd.org>1996-10-30 17:33:00 +0000
committerThomas Graichen <graichen@cvs.openbsd.org>1996-10-30 17:33:00 +0000
commitb85b15782739220b5bf2563ccdc7ac9e256a31d2 (patch)
tree5c87c07db954daa7e42a38261eec30f4241b2750 /usr.sbin/ctm/ctm_smail
parentae8ef4e6f9b691bbd8786a3242b8bbf3afe28f0a (diff)
import ctm (current through mail)
for more see http://www.openbsd.org/ctm.html - it's basicaly the FreeBSD version adapted to OpenBSD - i'll bring in the latest FreeBSD changes in the next days
Diffstat (limited to 'usr.sbin/ctm/ctm_smail')
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile7
-rw-r--r--usr.sbin/ctm/ctm_smail/ctm_smail.c591
2 files changed, 598 insertions, 0 deletions
diff --git a/usr.sbin/ctm/ctm_smail/Makefile b/usr.sbin/ctm/ctm_smail/Makefile
new file mode 100644
index 00000000000..e9eb84680ba
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/Makefile
@@ -0,0 +1,7 @@
+PROG= ctm_smail
+SRCS= ctm_smail.c error.c
+NOMAN= 1
+CFLAGS+= -Wall -I${.CURDIR}/../ctm_rmail
+.PATH: ${.CURDIR}/../ctm_rmail
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_smail/ctm_smail.c b/usr.sbin/ctm/ctm_smail/ctm_smail.c
new file mode 100644
index 00000000000..c995572ae61
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c
@@ -0,0 +1,591 @@
+/*
+ * Send a compressed CTM delta to a recipient mailing list by encoding it
+ * in safe ASCII characters, in mailer-friendly chunks, and passing it
+ * to sendmail. The encoding is almost the same as MIME BASE64, and is
+ * protected by a simple checksum.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ *
+ * $Id: ctm_smail.c,v 1.1 1996/10/30 17:32:59 graichen Exp $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <paths.h>
+#include "error.h"
+#include "options.h"
+
+#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */
+
+#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */
+
+void chop_and_send(char *delta, off_t ctm_size, long max_msg_size,
+ char *mail_alias);
+void chop_and_queue(char *delta, off_t ctm_size, long max_msg_size,
+ char *queue_dir, char *mail_alias);
+unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size);
+void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
+ int npieces);
+void write_trailer(FILE *sfp, unsigned sum);
+void apologise(char *delta, off_t ctm_size, long max_ctm_size,
+ char *mail_alias);
+FILE *open_sendmail(void);
+int close_sendmail(FILE *fp);
+int lock_queuedir(char *queue_dir);
+void free_lock(int lockf, char *queue_dir);
+void add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames);
+
+int
+main(int argc, char **argv)
+ {
+ char *delta_file;
+ char *mail_alias;
+ long max_msg_size = DEF_MAX_MSG;
+ long max_ctm_size = 0;
+ char *log_file = NULL;
+ char *queue_dir = NULL;
+ struct stat sb;
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias")
+ NUMBER('m', max_msg_size)
+ NUMBER('c', max_ctm_size)
+ STRING('l', log_file)
+ STRING('q', queue_dir)
+ ENDOPTS
+
+ if (argc != 3)
+ usage();
+
+ if (log_file != NULL)
+ err_set_log(log_file);
+
+ delta_file = argv[1];
+ mail_alias = argv[2];
+
+ if (stat(delta_file, &sb) < 0)
+ {
+ err("%s: %s", delta_file, strerror(errno));
+ exit(1);
+ }
+
+ if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
+ apologise(delta_file, sb.st_size, max_ctm_size, mail_alias);
+ else if (queue_dir == NULL)
+ chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias);
+ else
+ chop_and_queue(delta_file, sb.st_size, max_msg_size, queue_dir, mail_alias);
+
+ return 0;
+ }
+
+
+/*
+ * Carve our CTM delta into pieces, encode them, and send them.
+ */
+void
+chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias)
+ {
+ int npieces;
+ long msg_size;
+ long exp_size;
+ int pce;
+ FILE *sfp;
+ FILE *dfp;
+ unsigned sum;
+
+#ifdef howmany
+#undef howmany
+#endif
+
+#define howmany(x, y) (((x) + ((y) - 1)) / (y))
+
+ /*
+ * Work out how many pieces we need, bearing in mind that each piece
+ * grows by 4/3 when encoded. We count the newlines too, but ignore
+ * all mail headers and piece headers. They are a "small" (almost
+ * constant) per message overhead that we make the user worry about. :-)
+ */
+ exp_size = ctm_size * 4 / 3;
+ exp_size += howmany(exp_size, LINE_LENGTH);
+ npieces = howmany(exp_size, max_msg_size);
+ msg_size = howmany(ctm_size, npieces);
+
+#undef howmany
+
+ if ((dfp = fopen(delta, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading.", delta);
+ exit(1);
+ }
+
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ sfp = open_sendmail();
+ if (sfp == NULL)
+ exit(1);
+ write_header(sfp, mail_alias, delta, pce, npieces);
+ sum = encode_body(sfp, dfp, msg_size);
+ write_trailer(sfp, sum);
+ if (!close_sendmail(sfp))
+ exit(1);
+ err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
+ }
+
+ fclose(dfp);
+ }
+
+/*
+ * Carve our CTM delta into pieces, encode them, and drop them in the
+ * queue dir.
+ *
+ * Basic algorythm:
+ *
+ * - for (each piece)
+ * - gen. temp. file name (one which the de-queuer will ignore)
+ * - record in array
+ * - open temp. file
+ * - encode delta (including headers) into the temp file
+ * - close temp. file
+ * - end
+ * - lock queue directory
+ * - foreach (temp. file)
+ * - rename to the proper filename
+ * - end
+ * - unlock queue directory
+ *
+ * This is probably overkill, but it means that incomplete deltas
+ * don't get mailed, and also reduces the window for lock races
+ * between ctm_smail and the de-queueing process.
+ */
+
+void
+chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, char *queue_dir, char *mail_alias)
+{
+ int npieces, pce, len;
+ long msg_size, exp_size;
+ FILE *sfp, *dfp;
+ unsigned sum;
+ char **tempnames, *tempnam, *sn;
+
+#define howmany(x, y) (((x) + ((y) - 1)) / (y))
+
+ /*
+ * Work out how many pieces we need, bearing in mind that each piece
+ * grows by 4/3 when encoded. We count the newlines too, but ignore
+ * all mail headers and piece headers. They are a "small" (almost
+ * constant) per message overhead that we make the user worry about. :-)
+ */
+ exp_size = ctm_size * 4 / 3;
+ exp_size += howmany(exp_size, LINE_LENGTH);
+ npieces = howmany(exp_size, max_msg_size);
+ msg_size = howmany(ctm_size, npieces);
+
+#undef howmany
+
+ /*
+ * allocate space for the array of filenames. Try to be portable
+ * by not assuming anything to do with sizeof(char *)
+ */
+ tempnames = malloc(npieces * sizeof(char *));
+ if (tempnames == NULL)
+ {
+ err("malloc for tempnames failed");
+ exit(1);
+ }
+
+ len = strlen(queue_dir) + 16;
+ tempnam = malloc(len);
+ if (tempnam == NULL)
+ {
+ err("malloc for tempnames failed");
+ exit(1);
+ }
+
+ if ((dfp = fopen(delta, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading.", delta);
+ exit(1);
+ }
+
+ if ((sn = strrchr(delta, '/')) == NULL)
+ sn = delta;
+ else
+ sn++;
+
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ if (snprintf(tempnam, len, "%s/.%08d-%03d", queue_dir, getpid(), pce) >= len)
+ err("Whoops! tempnam isn't long enough");
+
+ tempnames[pce - 1] = strdup(tempnam);
+ if (tempnames[pce - 1] == NULL)
+ {
+ err("strdup failed for temp. filename");
+ exit(1);
+ }
+
+ sfp = fopen(tempnam, "w");
+ if (sfp == NULL)
+ exit(1);
+
+ write_header(sfp, mail_alias, delta, pce, npieces);
+ sum = encode_body(sfp, dfp, msg_size);
+ write_trailer(sfp, sum);
+
+ if (fclose(sfp) != 0)
+ exit(1);
+
+ err("%s %d/%d created succesfully", sn, pce, npieces);
+ }
+
+ add_to_queue(queue_dir, mail_alias, delta, npieces, tempnames);
+
+ fclose(dfp);
+
+}
+
+
+/*
+ * MIME BASE64 encode table.
+ */
+static char to_b64[0x40] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+ * This cheap plastic checksum effectively rotates our checksum-so-far
+ * left one, then adds the character. We only want 16 bits of it, and
+ * don't care what happens to the rest. It ain't much, but it's small.
+ */
+#define add_ck(sum,x) \
+ ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
+
+/*
+ * Encode the body. Use an encoding almost the same as MIME BASE64.
+ *
+ * Characters are read from delta_fp and encoded characters are written
+ * to sm_fp. At most 'msg_size' characters should be read from delta_fp.
+ *
+ * The body consists of lines of up to LINE_LENGTH characters. Each group
+ * of 4 characters encodes 3 input characters. Each output character encodes
+ * 6 bits. Thus 64 different characters are needed in this representation.
+ */
+unsigned
+encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size)
+ {
+ unsigned short cksum = 0xffff;
+ unsigned char *ip;
+ char *op;
+ int want, n, i;
+ unsigned char inbuf[LINE_LENGTH*3/4];
+ char outbuf[LINE_LENGTH+1];
+
+ /*
+ * Round up to the nearest line boundary, for the tiniest of gains,
+ * and lots of neatness. :-)
+ */
+ msg_size += (LINE_LENGTH*3/4) - 1;
+ msg_size -= msg_size % (LINE_LENGTH*3/4);
+
+ while (msg_size > 0)
+ {
+ want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf);
+ if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0)
+ break;
+ msg_size -= n;
+
+ for (i = 0; i < n; i++)
+ add_ck(cksum, inbuf[i]);
+
+ /*
+ * Produce a line of encoded data. Every line length will be a
+ * multiple of 4, except for, perhaps, the last line.
+ */
+ ip = inbuf;
+ op = outbuf;
+ while (n >= 3)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6];
+ *op++ = to_b64[ip[2] & 0x3f];
+ ip += 3;
+ n -= 3;
+ }
+ if (n > 0)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ if (n >= 2)
+ *op++ = to_b64[ip[1] << 2 & 0x3f];
+ }
+ *op++ = '\n';
+ fwrite(outbuf, sizeof(char), op - outbuf, sm_fp);
+ }
+
+ if (ferror(delta_fp))
+ {
+ err("error reading input file.");
+ exit(1);
+ }
+
+ if (ferror(sm_fp))
+ {
+ err("error writing encoded file");
+ exit(1);
+ }
+
+ return cksum;
+ }
+
+
+/*
+ * Write the mail header and data header.
+ */
+void
+write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces)
+ {
+ char *sn;
+
+ if ((sn = strrchr(delta, '/')) == NULL)
+ sn = delta;
+ else
+ sn++;
+
+ fprintf(sfp, "From: owner-%s\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", sn, pce, npieces);
+
+ fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, pce, npieces);
+ }
+
+
+/*
+ * Write the data trailer.
+ */
+void
+write_trailer(FILE *sfp, unsigned sum)
+ {
+ fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum);
+ }
+
+
+/*
+ * We're terribly sorry, but the delta is too big to send.
+ */
+void
+apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias)
+ {
+ FILE *sfp;
+ char *sn;
+
+ sfp = open_sendmail();
+ if (sfp == NULL)
+ exit(1);
+
+ if ((sn = strrchr(delta, '/')) == NULL)
+ sn = delta;
+ else
+ sn++;
+
+ fprintf(sfp, "From: %s-owner\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-notice %s\n\n", sn);
+
+ fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", sn,
+ (long)ctm_size, max_ctm_size);
+ fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n");
+
+ if (!close_sendmail(sfp))
+ exit(1);
+ }
+
+
+/*
+ * Start a pipe to sendmail. Sendmail will decode the destination
+ * from the message contents.
+ */
+FILE *
+open_sendmail()
+ {
+ FILE *fp;
+ char buf[100];
+
+ sprintf(buf, "%s -t", _PATH_SENDMAIL);
+ if ((fp = popen(buf, "w")) == NULL)
+ err("cannot start sendmail");
+ return fp;
+ }
+
+
+/*
+ * Close a pipe to sendmail. Sendmail will then do its bit.
+ * Return 1 on success, 0 on failure.
+ */
+int
+close_sendmail(FILE *fp)
+ {
+ int status;
+
+ fflush(fp);
+ if (ferror(fp))
+ {
+ err("error writing to sendmail");
+ return 0;
+ }
+
+ if ((status = pclose(fp)) != 0)
+ err("sendmail failed with status %d", status);
+
+ return (status == 0);
+ }
+
+/*
+ * Lock the queuedir so we're the only guy messing about in there.
+ */
+int
+lock_queuedir(char *queue_dir)
+{
+ int fp, len;
+ char *buffer;
+ struct stat sb;
+
+ len = strlen(queue_dir) + 8;
+
+ buffer = malloc(len);
+ if (buffer == NULL)
+ {
+ err("malloc failed in lock_queuedir");
+ exit(1);
+ }
+
+ if (snprintf(buffer, len, "%s/.lock", queue_dir) >= len)
+ err("Whoops. lock buffer too small in lock_queuedir");
+
+ /*
+ * We do our own lockfile scanning to avoid unlink races. 60
+ * seconds should be enough to ensure that we won't get more races
+ * happening between the stat and the open/flock.
+ */
+
+ while (stat(buffer, &sb) == 0)
+ sleep(60);
+
+ if ((fp = open(buffer, O_WRONLY | O_CREAT | O_EXLOCK, 0600)) < 0)
+ {
+ err("can't open `%s' in lock_queuedir", buffer);
+ exit(1);
+ }
+
+#ifdef __OpenBSD__
+ snprintf(buffer, len, "%8d", getpid());
+#else
+ snprintf(buffer, len, "%8ld", getpid());
+#endif
+ write(fp, buffer, 8);
+
+ free(buffer);
+
+ return(fp);
+}
+
+/*
+ * Lock the queuedir so we're the only guy messing about in there.
+ */
+void
+free_lock(int lockf, char *queue_dir)
+{
+ int len;
+ char *path;
+
+ /*
+ * Most important: free the lock before we do anything else!
+ */
+
+ close(lockf);
+
+ len = strlen(queue_dir) + 7;
+
+ path = malloc(len);
+ if (path == NULL)
+ {
+ err("malloc failed in free_lock");
+ exit(1);
+ }
+
+ if (snprintf(path, len, "%s/.lock", queue_dir) >= len)
+ err("lock path buffer too small in free_lock");
+
+ if (unlink(path) != 0)
+ {
+ err("can't unlink lockfile `%s'", path);
+ exit(1);
+ }
+
+ free(path);
+}
+
+/* move everything into the queue directory. */
+
+void
+add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames)
+{
+ char *queuefile, *sn;
+ int pce, len, lockf;
+
+ if ((sn = strrchr(delta, '/')) == NULL)
+ sn = delta;
+ else
+ sn++;
+
+ /* try to malloc all we need BEFORE entering the lock loop */
+
+ len = strlen(queue_dir) + strlen(sn) + 7;
+ queuefile = malloc(len);
+ if (queuefile == NULL)
+ {
+ err("can't malloc for queuefile");
+ exit(1);
+ }
+
+ /*
+ * We should be the only process mucking around in the queue
+ * directory while we add the new queue files ... it could be
+ * awkward if the de-queue process starts it's job while we're
+ * adding files ...
+ */
+
+ lockf = lock_queuedir(queue_dir);
+ for (pce = 0; pce < npieces; pce++)
+ {
+ struct stat sb;
+
+ if (snprintf(queuefile, len, "%s/%s+%03d", queue_dir, sn, pce + 1) >= len)
+ err("whoops, queuefile buffer is too small");
+
+ if (stat(queuefile, &sb) == 0)
+ {
+ err("WOAH! Queue file `%s' already exists! Bailing out.", queuefile);
+ free_lock(lockf, queue_dir);
+ exit(1);
+ }
+
+ rename(tempnames[pce], queuefile);
+ err("Queue file %s now exists", queuefile);
+ }
+
+ free_lock(lockf, queue_dir);
+
+ free(queuefile);
+}