CTM email tools.

Reviewed by:	phk
Submitted by:	Stephen McKay <syssgm@devetir.qld.gov.au>
This commit is contained in:
Poul-Henning Kamp 1995-01-31 19:12:53 +00:00
parent e322e6a55a
commit e86e7d0e07
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=6081
10 changed files with 1351 additions and 1 deletions

View File

@ -1,4 +1,4 @@
SUBDIR= ctm ctm_scan
SUBDIR= ctm ctm_scan ctm_rmail ctm_smail
.include <bsd.subdir.mk>

View File

@ -0,0 +1,5 @@
# $Id$
.if exists(${.CURDIR}/../../Makefile.inc)
.include "${.CURDIR}/../../Makefile.inc"
.endif

View File

@ -0,0 +1,6 @@
PROG= ctm_rmail
SRCS= ctm_rmail.c error.c
CFLAGS+= -Wall -g
MLINKS+= ctm_rmail.1 ctm_smail.1
.include <bsd.prog.mk>

View File

@ -0,0 +1,310 @@
.\" NOTICE: This is free documentation. I hope you get some use from these
.\" words. In return you should think about all the nice people who sweat
.\" blood to document their free software. Maybe you should write some
.\" documentation and give it away. Maybe with a free program attached!
.\"
.\" Author: Stephen McKay
.\"
.Dd January 15, 1995
.Os
.Dt CTM_MAIL 1
.Sh NAME
.Nm ctm_smail, ctm_rmail
.Nd send and receive
.Nm ctm
deltas via mail
.Sh SYNOPSIS
.Nm ctm_smail
.Op Fl l Ar log
.Op Fl m Ar maxmsgsize
.Op Fl c Ar maxctmsize
.Ar ctm-delta
.Ar mail-alias
.Nm ctm_rmail
.Op Fl D
.Op Fl l Ar log
.Op Fl p Ar piecedir
.Op Fl d Ar deltadir
.Op Fl b Ar basedir
.Op Ar
.Sh DESCRIPTION
In conjuction with the
.Xr ctm 1
command,
.Nm ctm_smail
and
.Nm ctm_rmail
are used to distribute changes to a source tree via email.
.Nm ctm_smail
is given a compressed
.Nm ctm
delta, and a mailing list to send it to. It splits the delta into manageable
pieces, encodes them as mail messages and sends them to the mailing list.
Each recipient uses
.Nm ctm_rmail
(either manually or automatically) to decode and reassemble the delta, and
optionally call
.Xr ctm 1
to apply it to the source tree.
At the moment,
only two source trees are distributed, and both by the same site. These are
the FreeBSD-current source and CVS trees, distributed by
.Li ref.tfs.com .
.Pp
Command line arguments for
.Nm ctm_smail :
.Bl -tag -width indent
.It Fl l Ar log
Instead of appearing on
.Em stderr ,
error diagnostics and informational messages (other than command line errors)
are time stamped and written to the file
.Em log .
.It Fl m Ar maxmsgsize
Limit the maximum size mail message that
.Nm ctm_smail
is allowed to send. It is approximate since mail headers and other niceties
are not counted in this limit. If not specified, it will default to 64000
bytes, leaving room for 1535 bytes of headers before the rumoured 64k mail
limit.
.It Fl c Ar maxctmsize
Limit the maximum size delta that will be sent. Deltas bigger that this
limit will cause an apology mail message to be sent to the mailing list.
This is to prevent massive changes overwhelming users' mail boxes. Note that
this is the size before encoding. Encoding causes a 4/3 size increase before
mail headers are added. If not specified, there is no limit.
.El
.Pp
.Ar ctm-delta
is the delta to be sent, and
.Ar mail-alias
is the mailing list to send the delta to.
The mail messages are sent using
.Xr sendmail 8 .
.Pp
Command line arguments for
.Nm ctm_rmail :
.Bl -tag -width indent
.It Fl l Ar log
Instead of appearing on
.Em stderr ,
error diagnostics and informational messages (other than command line errors)
are time stamped and written to the file
.Em log .
.It Fl p Ar piecedir
Collect pieces of deltas in this directory. Each piece corresponds to a
single mail message. Pieces are removed when complete deltas are built.
If this flag is not given, no input files will be read, but completed
deltas may still be applied with
.Xr ctm 1
if the
.Fl b
flag is given.
.It Fl d Ar deltadir
Collect completed deltas in this directory. Deltas are built from one or
more pieces when all pieces are present.
.It Fl b Ar basedir
Apply any completed deltas to this source tree. If this flag is not given,
deltas will be stored, but not applied. The user may then apply the deltas
manually, or by using
.Nm ctm_rmail
without the
.Fl p
flag.
Deltas will not be applied if they do not match the
.Li .ctm_status
file in
.Ar basedir
(or if
.Li .ctm_status
does not exist).
.It Fl D
Delete deltas after successful application by
.Xr ctm 1 .
It is probably a good idea to avoid this flag (and keep all the deltas)
as one of the possible future enhancements to
.Xr ctm 1
is the ability to recover small groups of files from a full set of deltas.
.El
.Pp
The file arguments (or
.Em stdin ,
if there are none) are scanned for delta pieces. Multiple delta pieces
can be read from a single file, so an entire maildrop can be scanned
and processed with a single command.
.Sh FILE FORMAT
Following are the important parts of an actual (very small) delta piece:
.Bd -literal
From: src-cur-owner
To: src-cur
Subject: ctm-mail src-cur.0003.gz 1/4
CTM_MAIL BEGIN src-cur.0003.gz 1 4
H4sIAAAAAAACA3VU72/bNhD9bP0VByQoEiyRSZEUSQP9kKTeYCR2gDTdsGFAwB/HRogtG5K8NCj6
v4+UZSdtUQh6Rz0eee/xaF/dzx8up3/MFlDkBNrGnbttAwyo1pxoRgoiBNX/QJ5d3c9/X8DcPGGo
lggkPiXngE4W1gUjKPJCYyk5MZRbIqmNW/ASglIFcdwIzTUxaAqhnCPcBqloKEkJVNDMF0Azk+Bo
dDzzk0Ods/+A5gXv9YyJHjMCtJwQNeESNma7hOmXDRxn
CTM_MAIL END 61065
.Ed
.Pp
The subject of the message always begins with
.Dq ctm-mail
followed by the name of the delta, which piece this is, and how many total
pieces there are. The data is bracketed by
.Dq CTM_MAIL BEGIN
and
.Dq CTM_MAIL END
lines, duplicating the information in the subject line, plus a simple checksum.
.Pp
If the delta exceeds
.Ar maxctmsize ,
then a message like this will be received instead:
.Bd -literal
From: src-cur-owner
To: src-cur
Subject: ctm-notice src-cur.0999.gz
src-cur.0999.gz is 792843 bytes. The limit is 300000 bytes.
You can retrieve this delta via ftpmail, or your good mate at the university.
.Ed
.Pp
You are then on your own!
.Sh EXAMPLES
To send delta 32 of
.Em src-cur
to a group of wonderful code hackers known to
.Xr sendmail 8
as
.Em src-guys ,
limiting the mail size to roughly 60000 bytes, you could use:
.Bd -literal -offset indent
ctm_smail -m 60000 /wherever/it/is/src-cur.0032.gz src-guys
.Ed
.Pp
To decode every
.Nm ctm-mail
message in your mailbox, assemble them into complete deltas, then apply
any deltas built or lying around, you could use:
.Bd -literal -offset indent
ctm_rmail -p ~/pieces -d ~/deltas -b /usr/ctm-src-cur $MAIL
.Ed
.Pp
(Note that no messages are deleted by
.Nm ctm_rmail .
Any mail reader could be used for that purpose.)
.Pp
To create a mail alias called
.Em receiver-dude
that will automatically decode and assemble deltas, but not apply them,
you could put the following lines in your
.Pa /etc/aliases
file (assuming the
.Pa /ctm/tmp
and
.Pa /ctm/deltas
directories and
.Pa /ctm/log
file are writable by user
.Em daemon
or group
.Em wheel ) :
.Bd -literal -offset indent
receiver-dude: "|ctm_rmail -p /ctm/tmp -d /ctm/deltas -l /ctm/log"
owner-receiver-dude: real_dude@wherever.you.like
.Ed
.Pp
The second line will catch failures and drop them into your regular mailbox,
or wherever else you like.
.Pp
To apply all the deltas collected, and delete those applied, you could use:
.Bd -literal -offset indent
ctm_rmail -d /ctm/deltas -b /ctm/src-cur -l /ctm/apply.log
.Ed
.Sh SECURITY
If you automatically take your mail and pass it to a file tree patcher, you
might think you are handing the keys to your system to the hackers! Happily,
the window for mischief is quite small.
.Nm ctm_rmail
is careful to write only to the directories given to it (by not believing any
.Dq /
characters in the delta name), and the latest
.Nm ctm
disallows absolute pathnames in files it manipulates, so the worst you
could lose are a few source tree files (recoverable from your deltas).
Since
.Nm ctm
requires that a
.Nm md5
checksum match before it touches a file, only fellow
source recipients would be able to generate a fake delta, and they're such
nice folk that they wouldn't even think of it! :-)
.Pp
Even this possibility could be removed by using cryptographic signatures.
A possible future enhancement would be to use
.Nm PGP
to provide a secure wrapper.
.\" This next request is for sections 1, 6, 7 & 8 only
.Sh ENVIRONMENT
If deltas are to be applied then
.Xr ctm 1
and
.Xr gunzip 1
must be in your
.Ev PATH .
.Sh FILES
.Bl -tag -width indent
.It Pa PIECEDIR/*
Pieces of deltas waiting for the rest.
.It Pa DELTADIR/*
Completed deltas.
.It Pa BASEDIR/.ctm_status
File containing name and number of the next delta to be applied to this
source tree.
.\" This next request is for sections 1, 6, 7 & 8 only
.\" (command return values (to shell) and fprintf/stderr type diagnostics)
.Sh DIAGNOSTICS
.Nm ctm_smail
and
.Nm ctm_rmail
return exit status 0 for success, and 1 for various failures.
.Nm ctm_rmail
is expected to be called from a mail transfer program, and thus signals
failure only when the input mail message should be bounced (preferably into
your regular maildrop, not back to the sender). In short, failure to
apply a completed delta with
.Nm ctm
is not considered an error important enough to bounce the mail, and
.Nm ctm_rmail
returns an exit status of 0.
.Pp
In normal operation,
.Nm ctm_smail
will report messages like:
.Bd -literal -offset indent
ctm_smail: src-cur.0250.gz 1/2 sent to src-guys
.Ed
.Pp
.Nm ctm_rmail
will report messages like:
.Bd -literal -offset indent
ctm_rmail: src-cur.0250.gz 1/2 stored
ctm_rmail: src-cur.0250.gz 2/2 stored
ctm_rmail: src-cur.0250.gz complete
.Ed
.Pp
These messages go to
.Em stderr
or to the log file. Messages from
.Nm ctm
turn up here too. Error messages should be self explanatory.
.\" The next request is for sections 2 and 3 error and signal handling only.
.\" .Sh ERRORS
.Sh SEE ALSO
.Xr ctm 1
(coming soon)
.\" .Sh STANDARDS
.\" .Sh HISTORY
.Sh AUTHOR
Stephen McKay <syssgm@devetir.qld.gov.au>
.\" .Sh BUGS

View File

@ -0,0 +1,486 @@
/*
* Accept one (or more) ASCII encoded chunks that together make a compressed
* CTM delta. Decode them and reconstruct the deltas. Any completed
* deltas may be passed to ctm for unpacking.
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "error.h"
#include "options.h"
#define CTM_STATUS ".ctm_status"
char *piece_dir = NULL; /* Where to store pieces of deltas. */
char *delta_dir = NULL; /* Where to store completed deltas. */
char *base_dir = NULL; /* The tree to apply deltas to. */
int delete_after = 0; /* Delete deltas after ctm applies them. */
void apply_complete(void);
int read_piece(char *input_file);
int combine_if_complete(char *delta, int pce, int npieces);
int decode_line(char *line, char *out_buf);
/*
* If given a '-p' flag, read encoded delta pieces from stdin or file
* arguments, decode them and assemble any completed deltas. If given
* a '-b' flag, pass any completed deltas to 'ctm' for application to
* the source tree. The '-d' flag is mandatory, but either of '-p' or
* '-b' can be omitted. If given the '-l' flag, notes and errors will
* be timestamped and written to the given file.
*
* Exit status is 0 for success or 1 for indigestible input. That is,
* 0 means the encode input pieces were decoded and stored, and 1 means
* some input was discarded. If a delta fails to apply, this won't be
* reflected in the exit status. In this case, the delta is left in
* 'deltadir'.
*/
main(int argc, char **argv)
{
char *log_file = NULL;
int status = 0;
err_prog_name(argv[0]);
OPTIONS("[-D] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
FLAG('D', delete_after)
STRING('p', piece_dir)
STRING('d', delta_dir)
STRING('b', base_dir)
STRING('l', log_file)
ENDOPTS
if (delta_dir == NULL || piece_dir == NULL && (base_dir == NULL || argc>1))
usage();
if (log_file != NULL)
err_set_log(log_file);
if (argc <= 1)
{
if (piece_dir != NULL)
status = read_piece(NULL);
}
else
{
while (*++argv != NULL)
status |= read_piece(*argv);
}
if (base_dir != NULL)
apply_complete();
return status;
}
/*
* Construct the file name of a piece of a delta.
*/
#define mk_piece_name(fn,d,p,n) \
sprintf((fn), "%s/%s+%d-%d", piece_dir, (d), (p), (n))
/*
* Construct the file name of an assembled delta.
*/
#define mk_delta_name(fn,d) \
sprintf((fn), "%s/%s", delta_dir, (d))
/*
* If the next required delta is now present, let ctm lunch on it and any
* contiguous deltas.
*/
void
apply_complete()
{
int i, dn;
FILE *fp, *ctm;
struct stat sb;
char class[20];
char delta[30];
char fname[1000];
char buf[2000];
char junk[2];
char here[1000];
sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
if ((fp = fopen(fname, "r")) == NULL)
return;
i = fscanf(fp, "%s %d %c", class, &dn, junk);
fclose(fp);
if (i != 2)
return;
/*
* We might need to convert the delta filename to an absolute pathname.
*/
here[0] = '\0';
if (delta_dir[0] != '/')
{
getcwd(here, sizeof(here)-1);
i = strlen(here) - 1;
if (i >= 0 && here[i] != '/')
{
here[++i] = '/';
here[++i] = '\0';
}
}
/*
* Keep applying deltas until we run out or something bad happens.
*/
for (;;)
{
sprintf(delta, "%s.%04d.gz", class, ++dn);
mk_delta_name(fname, delta);
if (stat(fname, &sb) < 0)
return;
sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
if ((ctm = popen(buf, "r")) == NULL)
{
err("ctm failed to apply %s", delta);
return;
}
while (fgets(buf, sizeof(buf), ctm) != NULL)
{
i = strlen(buf) - 1;
if (i >= 0 && buf[i] == '\n')
buf[i] = '\0';
err("ctm: %s", buf);
}
if (pclose(ctm) != 0)
{
err("ctm failed to apply %s", delta);
return;
}
if (delete_after)
unlink(fname);
err("%s applied%s", delta, delete_after ? " and deleted" : "");
}
}
/*
* 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))
/*
* Decode the data between BEGIN and END, and stash it in the staging area.
* Multiple pieces can be present in a single file, bracketed by BEGIN/END.
* If we have all pieces of a delta, combine them. Returns 0 on success,
* and 1 for any sort of failure.
*/
int
read_piece(char *input_file)
{
int status = 0;
FILE *ifp, *ofp;
int decoding = 0;
int line_no = 0;
int i, n;
int pce, npieces;
unsigned claimed_cksum;
unsigned short cksum;
char out_buf[200];
char line[200];
char delta[30];
char pname[1000];
char junk[2];
ifp = stdin;
if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
{
err("cannot open '%s' for reading", input_file);
return 1;
}
while (fgets(line, sizeof(line), ifp) != NULL)
{
line_no++;
/*
* Look for the beginning of an encoded piece.
*/
if (!decoding)
{
if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c", delta, &pce, &npieces, junk) == 3)
{
char *s;
while ((s = strchr(delta, '/')) != NULL)
*s = '_';
mk_piece_name(pname, delta, pce, npieces);
if ((ofp = fopen(pname, "w")) == NULL)
{
err("cannot open '%s' for writing", pname);
status++;
continue;
}
cksum = 0xffff;
decoding++;
}
continue;
}
/*
* We are decoding. Stop if we see the end flag.
*/
if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
{
int e;
decoding = 0;
fflush(ofp);
e = ferror(ofp);
fclose(ofp);
if (e)
err("error writing %s", pname);
if (cksum != claimed_cksum)
err("checksum: read %d, calculated %d", claimed_cksum, cksum);
if (e || cksum != claimed_cksum)
{
err("%s %d/%d discarded", delta, pce, npieces);
unlink(pname);
status++;
continue;
}
err("%s %d/%d stored", delta, pce, npieces);
if (!combine_if_complete(delta, pce, npieces))
status++;
continue;
}
/*
* Must be a line of encoded data. Decode it, sum it, and save it.
*/
n = decode_line(line, out_buf);
if (n < 0)
{
err("line %d: illegal character: '%c'", line_no, line[-n]);
err("%s %d/%d discarded", delta, pce, npieces);
fclose(ofp);
unlink(pname);
status++;
decoding = 0;
continue;
}
for (i = 0; i < n; i++)
add_ck(cksum, out_buf[i]);
fwrite(out_buf, sizeof(char), n, ofp);
}
if (decoding)
{
err("truncated file");
err("%s %d/%d discarded", delta, pce, npieces);
fclose(ofp);
unlink(pname);
status++;
}
if (ferror(ifp))
{
err("error reading %s", input_file == NULL ? "stdin" : input_file);
status++;
}
if (input_file != NULL)
fclose(ifp);
return (status != 0);
}
/*
* Put the pieces together to form a delta, if they are all present.
* Returns 1 on success (even if we didn't do anything), and 0 on failure.
*/
int
combine_if_complete(char *delta, int pce, int npieces)
{
int i;
FILE *dfp, *pfp;
int c;
struct stat sb;
char pname[1000];
char dname[1000];
/*
* All here?
*/
for (i = 1; i <= npieces; i++)
{
if (i == pce)
continue;
mk_piece_name(pname, delta, i, npieces);
if (stat(pname, &sb) < 0)
return 1;
}
mk_delta_name(dname, delta);
/*
* We can probably just rename() it in to place if it is a small delta.
*/
if (npieces == 1)
{
mk_piece_name(pname, delta, 1, 1);
if (rename(pname, dname) == 0)
{
err("%s complete", delta);
return 1;
}
}
if ((dfp = fopen(dname, "w")) == NULL)
{
err("cannot open '%s' for writing", dname);
return 0;
}
/*
* Ok, the hard way. Reconstruct the delta by reading each piece in order.
*/
for (i = 1; i <= npieces; i++)
{
mk_piece_name(pname, delta, i, npieces);
if ((pfp = fopen(pname, "r")) == NULL)
{
err("cannot open '%s' for reading", pname);
fclose(dfp);
unlink(dname);
return 0;
}
while ((c = getc(pfp)) != EOF)
putc(c, dfp);
fclose(pfp);
}
fflush(dfp);
if (ferror(dfp))
{
err("error writing '%s'", dname);
fclose(dfp);
unlink(dname);
return 0;
}
fclose(dfp);
/*
* Throw the pieces away.
*/
for (i = 1; i <= npieces; i++)
{
mk_piece_name(pname, delta, i, npieces);
unlink(pname);
}
err("%s complete", delta);
return 1;
}
/*
* MIME BASE64 decode table.
*/
static unsigned char from_b64[0x80] =
{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
};
/*
* Decode a line of ASCII into binary. Returns the number of bytes in
* the output buffer, or < 0 on indigestable input. Error output is
* the negative of the index of the inedible character.
*/
int
decode_line(char *line, char *out_buf)
{
unsigned char *ip = (unsigned char *)line;
unsigned char *op = (unsigned char *)out_buf;
unsigned long bits;
unsigned x;
for (;;)
{
if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
break;
bits = x << 18;
ip++;
if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
{
bits |= x << 12;
*op++ = bits >> 16;
ip++;
if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
{
bits |= x << 6;
*op++ = bits >> 8;
ip++;
if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
{
bits |= x;
*op++ = bits;
ip++;
}
}
}
}
if (*ip == '\0' || *ip == '\n')
return op - (unsigned char *)out_buf;
else
return -(ip - (unsigned char *)line);
}

View File

@ -0,0 +1,70 @@
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include "error.h"
static FILE *error_fp = NULL;
static char *prog = NULL;
/*
* Log errors to the given file.
*/
void
err_set_log(char *log_file)
{
FILE *fp;
if ((fp = fopen(log_file, "a")) == NULL)
err("cannot log to '%s'", log_file);
else
error_fp = fp;
}
/*
* Set the error prefix if not logging to a file.
*/
void
err_prog_name(char *name)
{
if ((prog = strrchr(name, '/')) == NULL)
prog = name;
else
prog++;
}
/*
* Log an error.
*/
void
err(char *fmt, ...)
{
va_list ap;
time_t now;
struct tm *tm;
FILE *fp;
if ((fp = error_fp) == NULL)
{
fp = stderr;
if (prog != NULL)
fprintf(fp, "%s: ", prog);
}
else
{
time(&now);
tm = localtime(&now);
fprintf(fp, "%04d-%02d-%02d %02d:%02d ", tm->tm_year+1900,
tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);
}
va_start(ap, fmt);
vfprintf(fp, fmt, ap);
va_end(ap);
fprintf(fp, "\n");
fflush(fp);
}

View File

@ -0,0 +1,3 @@
extern void err_set_log(char *log_file);
extern void err_prog_name(char *name);
extern void err(char *fmt, ...);

View File

@ -0,0 +1,139 @@
/*
* Macros for processing command arguments.
*
* Conforms closely to the command option requirements of intro(1) in System V
* and intro(C) in Xenix.
*
* A command consists of: cmdname [ options ] [ cmdarguments ]
*
* Options consist of a leading dash '-' and a flag letter. An argument may
* follow optionally preceded by white space.
* Options without arguments may be grouped behind a single dash.
* A dash on its own is interpreted as the end of the options and is retained
* as a command argument.
* A double dash '--' is interpreted as the end of the options and is discarded.
*
* For example:
* zap -xz -f flame -q34 -- -x
*
* where zap.c contains the following in main():
*
* OPTIONS("[-xz] [-q queue-id] [-f dump-file] user")
* FLAG('x', xecute)
* FLAG('z', zot)
* STRING('f', file)
* fp = fopen(file, "w");
* NUMBER('q', queue)
* ENDOPTS
*
* Results in:
* xecute = 1
* zot = 1
* file = "flame"
* fp = fopen("flame", "w")
* queue = 34
* argc = 2
* argv[0] = "zap"
* argv[1] = "-x"
*
* Should the user enter unknown flags or leave out required arguments,
* the message:
*
* Usage: zap [-xz] [-q queue-id] [-f dump-file] user
*
* will be printed. This message can be printed by calling pusage(), or
* usage(). usage() will also cause program termination with exit code 1.
*
* Author: Stephen McKay, February 1991
*
* Based on recollection of the original options.h produced at the University
* of Queensland by Ross Patterson (and possibly others).
*/
static char *O_usage;
static char *O_name;
extern long atol();
void
pusage()
{
/*
* Avoid gratuitously loading stdio.
*/
write(2, "Usage: ", 7);
write(2, O_name, strlen(O_name));
write(2, " ", 1);
write(2, O_usage, strlen(O_usage));
write(2, "\n", 1);
}
#define usage() (pusage(), exit(1))
#define OPTIONS(usage_msg) \
{ \
char O_cont; \
O_usage = (usage_msg); \
O_name = argv[0]; \
while (*++argv && **argv == '-') \
{ \
if ((*argv)[1] == '\0') \
break; \
argc--; \
if ((*argv)[1] == '-' && (*argv)[2] == '\0') \
{ \
argv++; \
break; \
} \
O_cont = 1; \
while (O_cont) \
switch (*++*argv) \
{ \
case '-': \
usage(); \
case '\0': \
O_cont = 0;
#define FLAG(x,flag) \
break; \
case (x): \
(flag) = 1;
#define CHAR(x,ch) \
break; \
case (x): \
O_cont = 0; \
if (*++*argv == '\0' && (--argc, *++argv == 0)) \
usage(); \
(ch) = **argv;
#define NUMBER(x,n) \
break; \
case (x): \
O_cont = 0; \
if (*++*argv == '\0' && (--argc, *++argv == 0)) \
usage(); \
(n) = atol(*argv);
#define STRING(x,str) \
break; \
case (x): \
O_cont = 0; \
if (*++*argv == '\0' && (--argc, *++argv == 0)) \
usage(); \
(str) = *argv;
#define SUFFIX(x,str) \
break; \
case (x): \
(str) = ++*argv; \
O_cont = 0;
#define ENDOPTS \
break; \
default: \
usage(); \
} \
} \
O_end: \
*--argv = O_name; \
}

View File

@ -0,0 +1,7 @@
PROG= ctm_smail
SRCS= ctm_smail.c error.c
NOMAN= 1
CFLAGS+= -Wall -g -I${.CURDIR}/../ctm_rmail
.include <bsd.prog.mk>
.PATH: ${.CURDIR}/../ctm_rmail

View File

@ -0,0 +1,324 @@
/*
* 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.
*/
#include <stdio.h>
#include <string.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);
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);
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;
struct stat sb;
err_prog_name(argv[0]);
OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] ctm-delta mail-alias")
NUMBER('m', max_msg_size)
NUMBER('c', max_ctm_size)
STRING('l', log_file)
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
chop_and_send(delta_file, sb.st_size, max_msg_size, 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;
#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);
}
/*
* 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 to sendmail");
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: %s-owner\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);
}