diff options
author | Thomas Graichen <graichen@cvs.openbsd.org> | 1996-10-30 17:33:00 +0000 |
---|---|---|
committer | Thomas Graichen <graichen@cvs.openbsd.org> | 1996-10-30 17:33:00 +0000 |
commit | b85b15782739220b5bf2563ccdc7ac9e256a31d2 (patch) | |
tree | 5c87c07db954daa7e42a38261eec30f4241b2750 /usr.sbin/ctm/ctm_smail | |
parent | ae8ef4e6f9b691bbd8786a3242b8bbf3afe28f0a (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/Makefile | 7 | ||||
-rw-r--r-- | usr.sbin/ctm/ctm_smail/ctm_smail.c | 591 |
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); +} |