summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/ctm/Makefile2
-rw-r--r--usr.sbin/ctm/Makefile.inc5
-rw-r--r--usr.sbin/ctm/ctm_rmail/Makefile6
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.1310
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.c486
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.c70
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.h3
-rw-r--r--usr.sbin/ctm/ctm_rmail/options.h139
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile7
-rw-r--r--usr.sbin/ctm/ctm_smail/ctm_smail.c324
10 files changed, 1351 insertions, 1 deletions
diff --git a/usr.sbin/ctm/Makefile b/usr.sbin/ctm/Makefile
index dd9a766..5987a96 100644
--- a/usr.sbin/ctm/Makefile
+++ b/usr.sbin/ctm/Makefile
@@ -1,4 +1,4 @@
-SUBDIR= ctm ctm_scan
+SUBDIR= ctm ctm_scan ctm_rmail ctm_smail
.include <bsd.subdir.mk>
diff --git a/usr.sbin/ctm/Makefile.inc b/usr.sbin/ctm/Makefile.inc
new file mode 100644
index 0000000..2ecf884
--- /dev/null
+++ b/usr.sbin/ctm/Makefile.inc
@@ -0,0 +1,5 @@
+# $Id$
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
diff --git a/usr.sbin/ctm/ctm_rmail/Makefile b/usr.sbin/ctm/ctm_rmail/Makefile
new file mode 100644
index 0000000..274a79d
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/Makefile
@@ -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>
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
new file mode 100644
index 0000000..a6dbc64
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
@@ -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
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.c b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
new file mode 100644
index 0000000..859b0ed
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
@@ -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);
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.c b/usr.sbin/ctm/ctm_rmail/error.c
new file mode 100644
index 0000000..be3581d
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.c
@@ -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);
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.h b/usr.sbin/ctm/ctm_rmail/error.h
new file mode 100644
index 0000000..b8bc452
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.h
@@ -0,0 +1,3 @@
+extern void err_set_log(char *log_file);
+extern void err_prog_name(char *name);
+extern void err(char *fmt, ...);
diff --git a/usr.sbin/ctm/ctm_rmail/options.h b/usr.sbin/ctm/ctm_rmail/options.h
new file mode 100644
index 0000000..829a913
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/options.h
@@ -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; \
+ }
diff --git a/usr.sbin/ctm/ctm_smail/Makefile b/usr.sbin/ctm/ctm_smail/Makefile
new file mode 100644
index 0000000..085899c
--- /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 -g -I${.CURDIR}/../ctm_rmail
+
+.include <bsd.prog.mk>
+.PATH: ${.CURDIR}/../ctm_rmail
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 0000000..b0379a2
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c
@@ -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);
+ }
OpenPOWER on IntegriCloud