diff options
Diffstat (limited to 'usr.sbin/ctm')
38 files changed, 5533 insertions, 0 deletions
diff --git a/usr.sbin/ctm/Makefile b/usr.sbin/ctm/Makefile new file mode 100644 index 0000000..630aab0 --- /dev/null +++ b/usr.sbin/ctm/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +SUBDIR= ctm ctm_rmail ctm_smail ctm_dequeue + +.include <bsd.subdir.mk> diff --git a/usr.sbin/ctm/Makefile.inc b/usr.sbin/ctm/Makefile.inc new file mode 100644 index 0000000..c6c2c5a --- /dev/null +++ b/usr.sbin/ctm/Makefile.inc @@ -0,0 +1,5 @@ +# $FreeBSD$ + +.if exists(${.CURDIR}/../../Makefile.inc) +.include "${.CURDIR}/../../Makefile.inc" +.endif diff --git a/usr.sbin/ctm/README b/usr.sbin/ctm/README new file mode 100644 index 0000000..94c779a --- /dev/null +++ b/usr.sbin/ctm/README @@ -0,0 +1,85 @@ +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <phk@FreeBSD.org> wrote this file. As long as you retain this notice you +# can do whatever you want with this stuff. If we meet some day, and you think +# this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp +# ---------------------------------------------------------------------------- +# +# $FreeBSD$ +# + +What will I not find in this file ? +----------------------------------- +Instructions on how to obtain FreeBSD via CTM. +Contact <CTM@FreeBSD.org> for that. + +What is CTM ? +------------- +CTM was originally "CVS Through eMail", but has since changed scope to be +much more general. +CTM is now meant to be the definitive way to make and apply a delta between +two versions of a directory tree. +There are two parts to this, making the delta and applying it. These are two +entirely different things. CTM concentrates the computation-burden on the +generation of the deltas, as a delta very often is applied more times than +it is made. Second CTM tries to make the minimal size delta. + +Why not use diff/patch ? +------------------------ +Good question. Primarily because diff and patch doesn't do their job very +well. They don't deal with binary files (in this case files with '\0' or +'\0377' characters in them or files that doesn't end in '\n') which isn't +a big surprise: they were made to deal with text-files only. As a second +gripe, with patch you send the entire file to delete it. Not particular +efficient. + +So what does CTM do exactly ? +----------------------------- +CTM will produce a file, (a delta) containing the instructions and data needed +to take another copy of the tree from the old to the new status. CTM means to +do this in the exact sense, and therefore the delta contains MD5 checksums to +verify that the tree it is applied to is indeed in the state CTM expects. + +This means that if you have modified the tree locally, CTM might not be able +to upgrade your copy. + +How do I make a CTM-delta ? +--------------------------- + +Read the source, and be prepared to have 2 copies of the tree; One is +the reference ("From") tree, and the other is the delta ("To") tree. +The mkCTM script will create the CTM diff of the differences between +the reference tree and the delta tree. A lot of scratch space is +required, and your machine will work hard. + +How do I apply a CTM-delta ? +---------------------------- +You pass it to the 'ctm' command. You can pass a CTM-delta on stdin, or +you can give the filename as an argument. If you do the latter, you make +life a lot easier for your self, since the program can accept gzip'ed files +and since it will not have to make a temporary copy of your file. You can +specify multiple deltas at one time, they will be processed one at a time. + +The ctm command runs in a number of passes. It will process the entire +input file in each pass, before commencing with the next pass. + +Pass 1 will validate that the input file is OK. The syntax, the data and +the global MD5 checksum will be checked. If any of these fail, ctm will +never be able to do anything with the file, so it will simply reject it. + +Pass 2 will validate that the directory tree is in the state expected by +the CTM-delta. This is done by looking for files and directories which +should/should not exists and by checking the MD5 checksums of files. + +Pass 3 will actually apply the delta. + +Should I delete the delta when I have applied it ? +-------------------------------------------------- +No. You might want to selectively reconstruct a file latter on. + +Why is CTM not being maintained? +-------------------------------- +Because CVSUP has improved on the concept quite a bit, and is now +the method of choice. + +Poul-Henning diff --git a/usr.sbin/ctm/ctm/Makefile b/usr.sbin/ctm/ctm/Makefile new file mode 100644 index 0000000..dd27ed2 --- /dev/null +++ b/usr.sbin/ctm/ctm/Makefile @@ -0,0 +1,24 @@ +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <phk@FreeBSD.org> wrote this file. As long as you retain this notice you +# can do whatever you want with this stuff. If we meet some day, and you think +# this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp +# ---------------------------------------------------------------------------- +# +# $FreeBSD$ + +PROG= ctm +MAN= ctm.1 ctm.5 +SRCS= ctm.c ctm_input.c ctm_pass1.c ctm_pass2.c ctm_pass3.c \ + ctm_passb.c ctm_syntax.c ctm_ed.c + +NOTYET= ctm_ed.c + +LIBADD= md + +WARNS?= 2 + +.if exists(${.CURDIR}/../../Makefile.inc) +.include "${.CURDIR}/../../Makefile.inc" +.endif +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm/Makefile.depend b/usr.sbin/ctm/ctm/Makefile.depend new file mode 100644 index 0000000..064e492 --- /dev/null +++ b/usr.sbin/ctm/ctm/Makefile.depend @@ -0,0 +1,19 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libmd \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/ctm/ctm/ctm.1 b/usr.sbin/ctm/ctm/ctm.1 new file mode 100644 index 0000000..6ba35c9 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.1 @@ -0,0 +1,335 @@ +.\" ---------------------------------------------------------------------------- +.\" "THE BEER-WARE LICENSE" (Revision 42): +.\" <joerg@FreeBSD.org> wrote this file. As long as you retain this notice you +.\" can do whatever you want with this stuff. If we meet some day, and you think +.\" this stuff is worth it, you can buy me a beer in return. Joerg Wunsch +.\" ---------------------------------------------------------------------------- +.\" +.\" This manual page is partially obtained from Poul-Hennings CTM README +.\" file. +.\" +.\" CTM and ctm(1) by <phk@FreeBSD.org> +.\" +.\" $FreeBSD$ +.\" +.Dd December 14, 2015 +.Dt CTM 1 +.Os +.Sh NAME +.Nm ctm +.Nd source code mirror program +.Sh SYNOPSIS +.Nm +.Op Fl cFklquv +.Op Fl b Ar basedir +.Op Fl B Ar backup-file +.Op Fl e Ar include-regex +.Op Fl t Ar tar-command +.Op Fl T Ar tmpdir +.Op Fl V Ar level +.Op Fl x Ar exclude-regex +.Ar +.Sh DESCRIPTION +The +.Nm +utility was originally +.Dq Cvs Through eMail , +but now instead it seems more fitting to call it +.Dq Current Through eMail . +.Pp +The +.Nm +utility is now meant to be the definitive way to make and apply a delta between +two versions of a directory tree. +.Pp +There are two parts to this, making the delta and applying it. +These are two +entirely different things. +.Ss Usage +To apply a CTM delta, you pass it to the +.Nm +command. +You can pass a CTM delta on stdin, or you can give the +filename as an argument. +If you do the latter, you make life a lot +easier for your self, since the program can accept gzip'ed files and +since it will not have to make a temporary copy of your file. +You can +specify multiple deltas at one time, they will be processed one at a +time. +Deltas that are already applied will be ignored. +.Pp +The +.Nm +command runs in a number of passes. +It will process the entire +input file in each pass, before commencing with the next pass. +.Pp +Before working on a file +.Ar name +.Nm +first checks for the existence of the file +.Ar name.ctm . +If this file exists, +.Nm +works on it instead. +.Pp +Pass 1 will verify that the input file is OK. +The syntax, the data +and the global MD5 checksum will be checked. +If any of these fail, +.Nm +will simply reject the input file. +.Pp +Pass 2 will validate that the directory tree is in the state expected by +the CTM delta. +This is done by looking for files and directories which +should/should not exist and by checking the MD5 checksums of files. +.Pp +If a +.Ar backup-file +had been specified using the +.Fl B +option, all files that would be modified by this +.Nm +invocation are backed up +to this file using the archiver command specified by the +.Fl t +option. +The default archiver command is +.Nm "tar -rf %s -T -" . +.Pp +Pass 3 will actually apply the delta. +.Pp +The list of files that would be modified by +.Nm +is subject to filtering regular expressions specified +using the +.Fl e +and +.Fl x +options. +The +.Fl e +and +.Fl x +options are applied in order of appearance on the command line. +The last +filter that matched a given file name determines whether the file would be +operated on or left alone by +.Nm . +.Pp +The +.Nm +utility +will extract the file hierarchy below its working directory. +Absolute +filenames or filenames containing references through +.Sq Pa .\& +and +.Sq Pa ..\& +are explicitly prohibited as a security measure. +.Ss Options +.Bl -tag -width indent +.It Fl b Ar basedir +Prepend the path +.Ar basedir +to every filename. +.It Fl B Ar backup-file +Backup all files that would be modified by this CTM run to +.Ar backup-file . +If any filters are specified using the +.Fl e +and +.Fl x +options, then the final set of files backed up are those that would be +modified by CTM after the filters are applied. +.It Fl c +Check it out, do not do anything. +.It Fl e Ar regular_expression +Match each name in the CTM file against +.Ar regular_expression , +and if it matches process the file, otherwise leave it alone. +There may be +any number of these options. +Use of this option disables the +.Pa .ctm_status +sequence number checks. +For example, the expression +.Ic ^usr.sbin/ctm +for example, will select the +.Pa usr.sbin/ctm +source directory and all pathnames under it. +.Pp +Pathnames can be disabled from being considered by CTM using the +.Fl x +option. +.It Fl F +Force. +.It Fl k +Keep files and directories and do not remove them even if the CTM file +specifies they are to be removed. +If the +.Fl B +option is specified, these files and directories will not be backed up. +.It Fl l +List files that would be modified by this invocation of CTM and the +actions that would be performed on them. +Use of the +.Fl l +option disables the +.Pa .ctm_status +checks and integrity checks on the source tree being operated on. +The +.Fl l +option can be combined with the +.Fl e +and +.Fl x +options to determine which files would be modified by the given set of +command line options. +.It Fl q +Tell us less. +.It Fl t Ar tar-command +Use +.Ar tar-command +instead of the default archiver +.Nm tar . +This option takes effect only if a backup file had been specified using the +.Fl B +option. +A %s in the tar command will be replaced by the name of the backup +file. +.It Fl T Ar tmpdir +Put temporary files under +.Ar tmpdir . +.It Fl u +Set modification time of created and modified files to the CTM delta +creation time. +.It Fl v +Tell us more. +.It Fl V Ar level +Tell us more. +.Ar Level +is the level of verbosity. +.It Fl x Ar regular_expression +Match each name in the CTM file against +.Ar regular_expression +and if it matches, leave the file alone. +There may be any number of these +options. +Use of this option disables the +.Pa .ctm_status +sequence number checks. +.Pp +Pathnames can be selected for CTM's consideration using the +.Fl e +option. +.El +.Sh SECURITY +On its own, CTM is an insecure protocol +- there is no authentication performed that the +changes applied to the source code were sent by a +trusted party, and so care should be taken if the +CTM deltas are obtained via an unauthenticated +medium such as regular email. +It is a relatively simple matter for an attacker +to forge a CTM delta to replace or precede the +legitimate one and insert malicious code into your +source tree. +If the legitimate delta is somehow prevented from +arriving, this will go unnoticed until a later +delta attempts to touch the same file, at which +point the MD5 checksum will fail. +.Pp +To remedy this insecurity, CTM pieces generated by +FreeBSD.org are cryptographically signed in a +format compatible with the GNU Privacy Guard +utility, available in /usr/ports/security/gpg, and +the Pretty Good Privacy v5 utility, +/usr/ports/security/pgp5. +The relevant public key can be obtained by +fingering ctm@FreeBSD.org. +.Pp +CTM deltas which are thus signed cannot be +undetectably altered by an attacker. +Therefore it is recommended that you make use of +GPG or PGP5 to verify the signatures if you +receive your CTM deltas via email. +.Sh ENVIRONMENT +.Ev TMPDIR , +if set to a pathname, will cause ctm to use that pathname +as the location of temporary file. +See +.Xr tempnam 3 , +for more details on this. +The same effect may be achieved with the +.Fl T +flag. +.Sh FILES +.Pa .ctm_status +contains the sequence number of the last CTM delta applied. +Changing +or removing this file will greatly confuse +.Nm . +.Pp +Using the +.Fl e +and +.Fl x +options can update a partial subset of the source tree and causes sources +to be in an inconsistent state. +It is assumed that you know what you are +doing when you use these options. +.Sh EXAMPLES +.Bd -literal +cd ~cvs +/usr/sbin/ctm ~ctm/cvs-* +.Ed +.Pp +To extract and patch all sources under `lib' +.Bd -literal +cd ~/lib-srcs +/usr/sbin/ctm -e '^lib' ~ctm/src-cur* +.Ed +.Sh DIAGNOSTICS +Numerous messages, hopefully self-explanatory. +The +.Dq noise level +can be adjusted with the +.Fl q , +.Fl v +and +.Fl V +options. +.Sh SEE ALSO +.Xr ctm_dequeue 1 , +.Xr ctm_rmail 1 , +.Xr ctm_smail 1 , +.Xr ctm 5 +.Rs +.%B "The FreeBSD Handbook" +.%T "Using CTM" +.%U http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/ctm.html +.Re +.Rs +.%T "Miscellaneous CTM on FreeBSD Resources" +.%U http://ctm.berklix.org +.Re +.Sh HISTORY +Initial trials were run during the work on +.Fx 1.1.5 , +and many bugs and +methods were hashed out. +.Pp +The +.Nm +command appeared in +.Fx 2.1 . +.Sh AUTHORS +.An -nosplit +The CTM system has been designed and implemented by +.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org . +.Pp +.An Joerg Wunsch Aq Mt joerg@FreeBSD.org +wrote this man-page. diff --git a/usr.sbin/ctm/ctm/ctm.5 b/usr.sbin/ctm/ctm/ctm.5 new file mode 100644 index 0000000..e48b09f --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.5 @@ -0,0 +1,182 @@ +.\" ---------------------------------------------------------------------------- +.\" "THE BEER-WARE LICENSE" (Revision 42): +.\" <joerg@FreeBSD.org> wrote this file. As long as you retain this notice you +.\" can do whatever you want with this stuff. If we meet some day, and you think +.\" this stuff is worth it, you can buy me a beer in return. Joerg Wunsch +.\" ---------------------------------------------------------------------------- +.\" +.\" This manual page is partially obtained from Poul-Hennings CTM README +.\" file. +.\" +.\" CTM and ctm(1) by <phk@FreeBSD.org> +.\" +.\" $FreeBSD$ +.\" +.Dd March 25, 1995 +.Dt CTM 5 +.Os +.Sh NAME +.Nm ctm +.Nd source code mirror system +.Sh DESCRIPTION +The +.Nm +transfers data in a specific file format, called a CTM delta. +.Pp +CTM deltas consist of control lines and data chunks. +Each control +line starts with the letters +.Dq CTM , +followed by a CTM statement and control data, and ends with a '\en' +character. +.Pp +Data chunks always belong to the preceding control line, and the +last field on that control line is the number of bytes in the data +chunk. +A trailing newline '\en' character follows each data chunk, this +newline is not part of the chunk and is not included in the count. +.Pp +The CTM statements are as follows. +.Bl -tag -width indent +.It _BEGIN Ar version name number timestamp prefix +This is the overall begin of a CTM delta file. +The +.Ar version +field must match the program version +(currently 2.0). +.Ar Name +is the name and +.Ar number +the sequence number of the CTM service, it is matched against the file +.Pa .ctm_status +to see if the delta has already been applied. +.Ar Timestamp +contains the year, month, day, hour, minute, and second of the +time of delta creation for reference +(followed by the letter +.Sq Z +meaning this is a UTC timestamp). +The +.Ar prefix +field is currently not implemented. +.It _END Ar md5 +This statement ends the CTM delta, the global +.Ar md5 +checksum is matched against the MD5 checksum of the entire delta, up to +and including the space (0x20) character following ``_END''. +.It \&FM Ar name uid gid mode md5 count +Make the file +.Ar name , +the original file had the uid +.Ar uid +(numerical, decimal), +the gid +.Ar gid +(numerical, decimal), +mode +.Ar mode +(numerical, octal), +and the MD5 checksum +.Ar md5 . +.Pp +The following +.Ar count +bytes data are the contents of the new file. +.It \&FS Ar name uid gid mode md5before md5after count +Substitute the contents of file +.Ar name , +the original file had the new uid +.Ar uid +(numerical, decimal), +the new gid +.Ar gid +(numerical, decimal), +new mode +.Ar mode +(numerical, octal), +the old MD5 checksum +.Ar md5before , +and the new MD5 checksum +.Ar md5after . +.Pp +The following +.Ar count +bytes data are the contents of the new file. +.Pp +File substitution is used if the commands to edit a file would exceed +the total file length, so substituting it is more efficient. +.It \&FN Ar name uid gid mode md5before md5after count +Edit the file +.Ar name . +The arguments are as above, but the data sections contains an +.Xr diff 1 +-n script which should be applied to the file in question. +.It \&FR Ar name md5 +Remove the file +.Ar name , +which must match the MD5 checksum +.Ar md5 . +.It \&AS Ar name uid gid mode +The original file +.Ar name +changed its owner to +.Ar uid , +its group to +.Ar gid , +and/or its mode to +.Ar mode . +.It \&DM Ar name uid gid mode +The directory +.Ar name +is to be created, it had originally the owner +.Ar uid , +group +.Ar gid , +and mode +.Ar mode . +.It \&DR Ar name +The directory +.Ar name +is to be removed. +.El +.Sh EXAMPLES +In the following example, long lines have been folded to make them +printable +(marked by backslashes). +.Bd -literal +CTM_BEGIN 2.0 cvs-cur 485 19950324214652Z . +CTMFR src/sys/gnu/i386/isa/scd.c,v 5225f13aa3c7e458f9dd0d4bb637b18d +CTMFR src/sys/gnu/i386/isa/scdreg.h,v e5af42b8a06f2c8030b93a7d71afb223 +CTMDM src/sys/gnu/i386/isa/Attic 0 552 775 +CTMFS .ctm_status 545 552 664 d9ccd2a84a9dbb8db56ba85663adebf0 \\ +e2a10c6f66428981782a0a18a789ee2e 12 +cvs-cur 485 + +CTMFN CVSROOT/commitlogs/gnu 545 552 664 \\ +5d7bc3549140d860bd9641b5782c002d 7fb04ed84b48160c9b8eea84b4c0b6e3 394 +a6936 21 +ache 95/03/24 09:59:50 + + Modified: gnu/lib/libdialog kernel.c prgbox.c + Log: +[...] +CTM_END 74ddd298d76215ae45a077a4b6a74e9c +.Ed +.Sh SEE ALSO +.Xr ctm 1 , +.Xr ctm_rmail 1 , +.Xr ed 1 +.Sh HISTORY +Initial trials ran during the +.Fx 1.1.5 , +and many bugs and +methods were hashed out. +The CTM system has been made publicly available in +.Fx 2.1 . +.Sh AUTHORS +.An -nosplit +The CTM system has been designed and implemented by +.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org . +.Pp +.An Joerg Wunsch Aq Mt joerg@FreeBSD.org +wrote this man-page. diff --git a/usr.sbin/ctm/ctm/ctm.c b/usr.sbin/ctm/ctm/ctm.c new file mode 100644 index 0000000..0ea4895 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.c @@ -0,0 +1,331 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + * This is the client program of 'CTM'. It will apply a CTM-patch to a + * collection of files. + * + * Options we'd like to see: + * + * -a Attempt best effort. + * -d <int> Debug TBD. + * -m <mail-addr> Email me instead. + * -r <name> Reconstruct file. + * -R <file> Read list of files to reconstruct. + * + * Options we have: + * -b <dir> Base-dir + * -B <file> Backup to tar-file. + * -t Tar command (default as in TARCMD). + * -c Check it out, don't do anything. + * -F Force + * -q Tell us less. + * -T <tmpdir>. Temporary files. + * -u Set all file modification times to the timestamp + * -v Tell us more. + * -V <level> Tell us more level = number of -v + * -k Keep files and directories that would have been removed. + * -l List actions. + * + * Options we don't actually use: + * -p Less paranoid. + * -P Paranoid. + */ + +#define EXTERN /* */ +#include <paths.h> +#include "ctm.h" + +#define CTM_STATUS ".ctm_status" + +extern int Proc(char *, unsigned applied); + +int +main(int argc, char **argv) +{ + int stat=0, err=0; + int c; + unsigned applied = 0; + FILE *statfile; + struct CTM_Filter *nfilter = NULL; /* new filter */ + u_char * basedir; + + basedir = NULL; + Verbose = 1; + Paranoid = 1; + SetTime = 0; + KeepIt = 0; + ListIt = 0; + BackupFile = NULL; + TarCmd = TARCMD; + LastFilter = FilterList = NULL; + TmpDir = getenv("TMPDIR"); + if (TmpDir == NULL) + TmpDir = strdup(_PATH_TMP); + setbuf(stderr,0); + setbuf(stdout,0); + + while((c=getopt(argc,argv,"ab:B:cd:e:Fklm:pPqr:R:t:T:uV:vx:")) != -1) { + switch (c) { + case 'b': basedir = optarg; break; /* Base Directory */ + case 'B': BackupFile = optarg; break; + case 'c': CheckIt++; break; /* Only check it */ + case 'F': Force = 1; break; + case 'k': KeepIt++; break; /* Don't do removes */ + case 'l': ListIt++; break; /* Only list actions and files */ + case 'p': Paranoid--; break; /* Less Paranoid */ + case 'P': Paranoid++; break; /* More Paranoid */ + case 'q': Verbose--; break; /* Quiet */ + case 't': TarCmd = optarg; break; /* archiver command */ + case 'T': TmpDir = optarg; break; /* set temporary directory */ + case 'u': SetTime++; break; /* Set timestamp on files */ + case 'v': Verbose++; break; /* Verbose */ + case 'V': sscanf(optarg,"%d", &c); /* Verbose */ + Verbose += c; + break; + case 'e': /* filter expressions */ + case 'x': + if (NULL == (nfilter = Malloc(sizeof(struct CTM_Filter)))) { + warnx("out of memory for expressions: \"%s\"", optarg); + stat++; + break; + } + + (void) memset(nfilter, 0, sizeof(struct CTM_Filter)); + + if (0 != (err = + regcomp(&nfilter->CompiledRegex, optarg, REG_NOSUB))) { + + char errmsg[128]; + + regerror(err, &nfilter->CompiledRegex, errmsg, + sizeof(errmsg)); + warnx("regular expression: \"%s\"", errmsg); + stat++; + break; + } + + /* note whether the filter enables or disables on match */ + nfilter->Action = + (('e' == c) ? CTM_FILTER_ENABLE : CTM_FILTER_DISABLE); + + /* link in the expression into the list */ + nfilter->Next = NULL; + if (NULL == FilterList) { + LastFilter = FilterList = nfilter; /* init head and tail */ + } else { /* place at tail */ + LastFilter->Next = nfilter; + LastFilter = nfilter; + } + break; + case ':': + warnx("option '%c' requires an argument",optopt); + stat++; + break; + case '?': + warnx("option '%c' not supported",optopt); + stat++; + break; + default: + warnx("option '%c' not yet implemented",optopt); + break; + } + } + + if(stat) { + warnx("%d errors during option processing",stat); + return Exit_Pilot; + } + stat = Exit_Done; + argc -= optind; + argv += optind; + + if (basedir == NULL) { + Buffer = (u_char *)Malloc(BUFSIZ + strlen(SUBSUFF) +1); + CatPtr = Buffer; + *Buffer = '\0'; + } else { + Buffer = (u_char *)Malloc(strlen(basedir)+ BUFSIZ + strlen(SUBSUFF) +1); + strcpy(Buffer, basedir); + CatPtr = Buffer + strlen(basedir); + if (CatPtr[-1] != '/') { + strcat(Buffer, "/"); + CatPtr++; + } + } + strcat(Buffer, CTM_STATUS); + + if(ListIt) + applied = 0; + else + if((statfile = fopen(Buffer, "r")) == NULL) { + if (Verbose > 0) + warnx("warning: %s not found", Buffer); + } else { + fscanf(statfile, "%*s %u", &applied); + fclose(statfile); + } + + if(!argc) + stat |= Proc("-", applied); + + while(argc-- && stat == Exit_Done) { + stat |= Proc(*argv++, applied); + stat &= ~(Exit_Version | Exit_NoMatch); + } + + if(stat == Exit_Done) + stat = Exit_OK; + + if(Verbose > 0) + warnx("exit(%d)",stat); + + if (FilterList) + for (nfilter = FilterList; nfilter; ) { + struct CTM_Filter *tmp = nfilter->Next; + Free(nfilter); + nfilter = tmp; + } + return stat; +} + +int +Proc(char *filename, unsigned applied) +{ + FILE *f; + int i; + char *p = strrchr(filename,'.'); + + if(!strcmp(filename,"-")) { + p = 0; + f = stdin; + } else if(p && (!strcmp(p,".gz") || !strcmp(p,".Z"))) { + p = alloca(20 + strlen(filename)); + strcpy(p,"gunzip < "); + strcat(p,filename); + f = popen(p,"r"); + if(!f) { warn("%s", p); return Exit_Garbage; } + } else { + p = 0; + f = fopen(filename,"r"); + } + if(!f) { + warn("%s", filename); + return Exit_Garbage; + } + + if(Verbose > 1) + fprintf(stderr,"Working on <%s>\n",filename); + + Delete(FileName); + FileName = String(filename); + + /* If we cannot seek, we're doomed, so copy to a tmp-file in that case */ + if(!p && -1 == fseek(f,0,SEEK_END)) { + char *fn; + FILE *f2; + int fd; + + if (asprintf(&fn, "%s/CTMclient.XXXXXXXXXX", TmpDir) == -1) { + fprintf(stderr, "Cannot allocate memory\n"); + fclose(f); + return Exit_Broke; + } + if ((fd = mkstemp(fn)) == -1 || (f2 = fdopen(fd, "w+")) == NULL) { + warn("%s", fn); + free(fn); + if (fd != -1) + close(fd); + fclose(f); + return Exit_Broke; + } + unlink(fn); + if (Verbose > 0) + fprintf(stderr,"Writing tmp-file \"%s\"\n",fn); + free(fn); + while(EOF != (i=getc(f))) + if(EOF == putc(i,f2)) { + fclose(f2); + return Exit_Broke; + } + fclose(f); + f = f2; + } + + if(!p) + rewind(f); + + if((i=Pass1(f, applied))) + goto exit_and_close; + + if(ListIt) { + i = Exit_Done; + goto exit_and_close; + } + + if(!p) { + rewind(f); + } else { + pclose(f); + f = popen(p,"r"); + if(!f) { warn("%s", p); return Exit_Broke; } + } + + i=Pass2(f); + + if(!p) { + rewind(f); + } else { + pclose(f); + f = popen(p,"r"); + if(!f) { warn("%s", p); return Exit_Broke; } + } + + if(i) { + if((!Force) || (i & ~Exit_Forcible)) + goto exit_and_close; + } + + if(CheckIt) { + if (Verbose > 0) + fprintf(stderr,"All checks out ok.\n"); + i = Exit_Done; + goto exit_and_close; + } + + /* backup files if requested */ + if(BackupFile) { + + i = PassB(f); + + if(!p) { + rewind(f); + } else { + pclose(f); + f = popen(p,"r"); + if(!f) { warn("%s", p); return Exit_Broke; } + } + } + + i=Pass3(f); + +exit_and_close: + if(!p) + fclose(f); + else + pclose(f); + + if(i) + return i; + + if (Verbose > 0) + fprintf(stderr,"All done ok\n"); + + return Exit_Done; +} diff --git a/usr.sbin/ctm/ctm/ctm.h b/usr.sbin/ctm/ctm/ctm.h new file mode 100644 index 0000000..b733539 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.h @@ -0,0 +1,163 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <md5.h> +#include <regex.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/time.h> + +#define VERSION "2.0" + +#define SUBSUFF ".ctm" +#define TMPSUFF ".ctmtmp" +#define TARCMD "tar -rf %s -T -" + +/* The fields... */ +#define CTM_F_MASK 0xff +#define CTM_F_Name 0x01 +#define CTM_F_Uid 0x02 +#define CTM_F_Gid 0x03 +#define CTM_F_Mode 0x04 +#define CTM_F_MD5 0x05 +#define CTM_F_Count 0x06 +#define CTM_F_Bytes 0x07 + +/* The qualifiers... */ +#define CTM_Q_MASK 0xff00 +#define CTM_Q_Name_File 0x0100 +#define CTM_Q_Name_Dir 0x0200 +#define CTM_Q_Name_New 0x0400 +#define CTM_Q_Name_Subst 0x0800 +#define CTM_Q_MD5_After 0x0100 +#define CTM_Q_MD5_Before 0x0200 +#define CTM_Q_MD5_Chunk 0x0400 +#define CTM_Q_MD5_Force 0x0800 + +struct CTM_Syntax { + char *Key; /* CTM key for operation */ + int *List; /* List of operations */ + }; + +extern struct CTM_Syntax Syntax[]; + +struct CTM_Filter { + struct CTM_Filter *Next; /* next filter in the list */ + int Action; /* enable or disable */ + regex_t CompiledRegex; /* compiled regex */ +}; + +#define CTM_FILTER_DISABLE 0 +#define CTM_FILTER_ENABLE 1 + +#define Malloc malloc +#define Free free +#define Delete(foo) if (!foo) ; else {Free(foo); foo = 0; } +#define String(foo) strdup(foo) + +#ifndef EXTERN +# define EXTERN extern +#endif +EXTERN u_char *Version; +EXTERN u_char *Name; +EXTERN u_char *Nbr; +EXTERN u_char *TimeStamp; +EXTERN u_char *Prefix; +EXTERN u_char *FileName; +EXTERN u_char *TmpDir; +EXTERN u_char *CatPtr; +EXTERN u_char *Buffer; +EXTERN u_char *BackupFile; +EXTERN u_char *TarCmd; + +/* + * Paranoid -- Just in case they should be after us... + * 0 not at all. + * 1 normal. + * 2 somewhat. + * 3 you bet!. + * + * Verbose -- What to tell mom... + * 0 Nothing which wouldn't surprise. + * 1 Normal. + * 2 Show progress '.'. + * 3 Show progress names, and actions. + * 4 even more... + * and so on + * + * ExitCode -- our Epitaph + * 0 Perfect, all input digested, no problems + * 1 Bad input, no point in retrying. + * 2 Pilot error, commandline problem &c + * 4 Out of resources. + * 8 Destination-tree not correct. + * 16 Destination-tree not correct, can force. + * 32 Internal problems. + * + */ + +EXTERN int Paranoid; +EXTERN int Verbose; +EXTERN int Exit; +EXTERN int Force; +EXTERN int CheckIt; +EXTERN int KeepIt; +EXTERN int ListIt; +EXTERN int SetTime; +EXTERN struct timeval Times[2]; +EXTERN struct CTM_Filter *FilterList; +EXTERN struct CTM_Filter *LastFilter; + +#define Exit_OK 0 +#define Exit_Garbage 1 +#define Exit_Pilot 2 +#define Exit_Broke 4 +#define Exit_NotOK 8 +#define Exit_Forcible 16 +#define Exit_Mess 32 +#define Exit_Done 64 +#define Exit_Version 128 +#define Exit_NoMatch 256 + +void Fatal_(int ln, char *fn, char *kind); +#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo) +#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.") +#define WRONG {Assert(); return Exit_Mess;} + +u_char * Ffield(FILE *fd, MD5_CTX *ctx,u_char term); +u_char * Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose); + +int Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term); + +u_char * Fdata(FILE *fd, int u_chars, MD5_CTX *ctx); + +#define GETFIELD(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD +#define GETFIELDCOPY(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD; else p=String(p) +#define GETBYTECNT(p,q) if(0 >((p)= Fbytecnt(fd,&ctx,(q)))) return BADREAD +#define GETDATA(p,q) if(!((p) = Fdata(fd,(q),&ctx))) return BADREAD +#define GETNAMECOPY(p,q,r,v) if(!((p)=Fname(fd,&ctx,(q),(r),(v)))) return BADREAD; else p=String(p) + +int Pass1(FILE *fd, unsigned applied); +int Pass2(FILE *fd); +int PassB(FILE *fd); +int Pass3(FILE *fd); + +int ctm_edit(u_char *script, int length, char *filein, char *fileout); diff --git a/usr.sbin/ctm/ctm/ctm_ed.c b/usr.sbin/ctm/ctm/ctm_ed.c new file mode 100644 index 0000000..e3ec464 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_ed.c @@ -0,0 +1,114 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" + +int +ctm_edit(u_char *script, int length, char *filein, char *fileout) +{ + u_char *ep, cmd; + int ln, ln2, iln, ret=0, c; + FILE *fi=0,*fo=0; + + fi = fopen(filein,"r"); + if(!fi) { + warn("%s", filein); + return 8; + } + + fo = fopen(fileout,"w"); + if(!fo) { + warn("%s", fileout); + fclose(fi); + return 4; + } + iln = 1; + for(ep=script;ep < script+length;) { + cmd = *ep++; + if(cmd != 'a' && cmd != 'd') { ret = 1; goto bye; } + ln = 0; + while(isdigit(*ep)) { + ln *= 10; + ln += (*ep++ - '0'); + } + if(*ep++ != ' ') { ret = 1; goto bye; } + ln2 = 0; + while(isdigit(*ep)) { + ln2 *= 10; + ln2 += (*ep++ - '0'); + } + if(*ep++ != '\n') { ret = 1; goto bye; } + + + if(cmd == 'd') { + while(iln < ln) { + c = getc(fi); + if(c == EOF) { ret = 1; goto bye; } + putc(c,fo); + if(c == '\n') + iln++; + } + while(ln2) { + c = getc(fi); + if(c == EOF) { ret = 1; goto bye; } + if(c != '\n') + continue; + ln2--; + iln++; + } + continue; + } + if(cmd == 'a') { + while(iln <= ln) { + c = getc(fi); + if(c == EOF) { ret = 1; goto bye; } + putc(c,fo); + if(c == '\n') + iln++; + } + while(ln2) { + c = *ep++; + putc(c,fo); + if(c != '\n') + continue; + ln2--; + } + continue; + } + ret = 1; + goto bye; + } + while(1) { + c = getc(fi); + if(c == EOF) break; + putc(c,fo); + } + ret = 0; +bye: + if(fi) { + if(fclose(fi) != 0) { + warn("%s", filein); + ret = 1; + } + } + if(fo) { + if(fflush(fo) != 0) { + warn("%s", fileout); + ret = 1; + } + if(fclose(fo) != 0) { + warn("%s", fileout); + ret = 1; + } + } + return ret; +} diff --git a/usr.sbin/ctm/ctm/ctm_input.c b/usr.sbin/ctm/ctm/ctm_input.c new file mode 100644 index 0000000..32166d3 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_input.c @@ -0,0 +1,134 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" + +/*---------------------------------------------------------------------------*/ +void +Fatal_(int ln, char *fn, char *kind) +{ + if(Verbose > 2) + fprintf(stderr,"Fatal error. (%s:%d)\n",fn,ln); + fprintf(stderr,"%s Fatal error: %s\n",FileName, kind); +} +#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo) +#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.") + +/*---------------------------------------------------------------------------*/ +/* get next field, check that the terminating whitespace is what we expect */ +u_char * +Ffield(FILE *fd, MD5_CTX *ctx,u_char term) +{ + static u_char buf[BUFSIZ]; + int i,l; + + for(l=0;;) { + if((i=getc(fd)) == EOF) { + Fatal("Truncated patch."); + return 0; + } + buf[l++] = i; + if(isspace(i)) + break; + if(l >= sizeof buf) { + Fatal("Corrupt patch."); + printf("Token is too long.\n"); + return 0; + } + } + buf[l] = '\0'; + MD5Update(ctx,buf,l); + if(buf[l-1] != term) { + Fatal("Corrupt patch."); + fprintf(stderr,"Expected \"%s\" but didn't find it {%02x}.\n", + term == '\n' ? "\\n" : " ",buf[l-1]); + if(Verbose > 4) + fprintf(stderr,"{%s}\n",buf); + return 0; + } + buf[--l] = '\0'; + if(Verbose > 4) + fprintf(stderr,"<%s>\n",buf); + return buf; +} + +int +Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term) +{ + u_char *p,*q; + int u_chars=0; + + p = Ffield(fd,ctx,term); + if(!p) return -1; + for(q=p;*q;q++) { + if(!isdigit(*q)) { + Fatal("Bytecount contains non-digit."); + return -1; + } + u_chars *= 10; + u_chars += (*q - '0'); + } + return u_chars; +} + +u_char * +Fdata(FILE *fd, int u_chars, MD5_CTX *ctx) +{ + u_char *p = Malloc(u_chars+1); + + if(u_chars+1 != fread(p,1,u_chars+1,fd)) { + Fatal("Truncated patch."); + return 0; + } + MD5Update(ctx,p,u_chars+1); + if(p[u_chars] != '\n') { + if(Verbose > 3) + printf("FileData wasn't followed by a newline.\n"); + Fatal("Corrupt patch."); + return 0; + } + p[u_chars] = '\0'; + return p; +} + +/*---------------------------------------------------------------------------*/ +/* get the filename in the next field, prepend BaseDir and give back the result + strings. The sustitute filename is return (the one with the suffix SUBSUFF) + if it exists and the qualifier contains CTM_Q_Name_Subst + NOTA: Buffer is already initialize with BaseDir, CatPtr is the insertion + point on this buffer + the length test in Ffield() is enough for Fname() */ + +u_char * +Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose) +{ + u_char * p; + struct stat st; + + if ((p = Ffield(fd,ctx,term)) == NULL) return(NULL); + + strcpy(CatPtr, p); + + if (!(qual & CTM_Q_Name_Subst)) return(Buffer); + + p = Buffer + strlen(Buffer); + + strcat(Buffer, SUBSUFF); + + if ( -1 == stat(Buffer, &st) ) { + *p = '\0'; + } else { + if(verbose > 2) + fprintf(stderr,"Using %s as substitute file\n", Buffer); + } + + return (Buffer); +} diff --git a/usr.sbin/ctm/ctm/ctm_pass1.c b/usr.sbin/ctm/ctm/ctm_pass1.c new file mode 100644 index 0000000..026a320 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass1.c @@ -0,0 +1,250 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" +#define BADREAD 1 + +/*---------------------------------------------------------------------------*/ +/* Pass1 -- Validate the incoming CTM-file. + */ + +int +Pass1(FILE *fd, unsigned applied) +{ + u_char *p,*q; + MD5_CTX ctx; + int i,j,sep,cnt; + u_char *md5=0,*name=0,*trash=0; + struct CTM_Syntax *sp; + int slashwarn=0, match=0, total_matches=0; + unsigned current; + char md5_1[33]; + + if(Verbose>3) + printf("Pass1 -- Checking integrity of incoming CTM-patch\n"); + MD5Init (&ctx); + + GETFIELD(p,' '); /* CTM_BEGIN */ + if(strcmp(p,"CTM_BEGIN")) { + Fatal("Probably not a CTM-patch at all."); + if(Verbose>3) + fprintf(stderr,"Expected \"CTM_BEGIN\" got \"%s\".\n",p); + return 1; + } + + GETFIELDCOPY(Version,' '); /* <Version> */ + if(strcmp(Version,VERSION)) { + Fatal("CTM-patch is wrong version."); + if(Verbose>3) + fprintf(stderr,"Expected \"%s\" got \"%s\".\n",VERSION,p); + return 1; + } + + GETFIELDCOPY(Name,' '); /* <Name> */ + GETFIELDCOPY(Nbr,' '); /* <Nbr> */ + GETFIELDCOPY(TimeStamp,' '); /* <TimeStamp> */ + GETFIELDCOPY(Prefix,'\n'); /* <Prefix> */ + + sscanf(Nbr, "%u", ¤t); + if (FilterList || ListIt) + current = 0; /* ignore if -l or if filters are present */ + if(current && current <= applied) { + if(Verbose > 0) + fprintf(stderr,"Delta number %u is already applied; ignoring.\n", + current); + return Exit_Version; + } + + for(;;) { + Delete(md5); + Delete(name); + Delete(trash); + cnt = -1; + /* if a filter list is defined we assume that all pathnames require + an action opposite to that requested by the first filter in the + list. + If no filter is defined, all pathnames are assumed to match. */ + match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE); + + GETFIELD(p,' '); /* CTM_something */ + + if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') { + Fatal("Expected CTM keyword."); + fprintf(stderr,"Got [%s]\n",p); + return 1; + } + + if(!strcmp(p+3,"_END")) + break; + + for(sp=Syntax;sp->Key;sp++) + if(!strcmp(p+3,sp->Key)) + goto found; + Fatal("Expected CTM keyword."); + fprintf(stderr,"Got [%s]\n",p); + return 1; + found: + if(Verbose > 5) + fprintf(stderr,"%s ",sp->Key); + for(i=0;(j = sp->List[i]);i++) { + if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes) + sep = ' '; + else + sep = '\n'; + + if(Verbose > 5) + fprintf(stderr," %x(%d)",sp->List[i],sep); + + switch (j & CTM_F_MASK) { + case CTM_F_Name: /* XXX check for garbage and .. */ + GETFIELDCOPY(name,sep); + j = strlen(name); + if(name[j-1] == '/' && !slashwarn) { + fprintf(stderr,"Warning: contains trailing slash\n"); + slashwarn++; + } + if (name[0] == '/') { + Fatal("Absolute paths are illegal."); + return Exit_Mess; + } + q = name; + for (;;) { + if (q[0] == '.' && q[1] == '.') + if (q[2] == '/' || q[2] == '\0') { + Fatal("Paths containing '..' are illegal."); + return Exit_Mess; + } + if ((q = strchr(q, '/')) == NULL) + break; + q++; + } + + /* if we have been asked to `keep' files then skip + removes; i.e. we don't match these entries at + all. */ + if (KeepIt && + (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR"))) { + match = CTM_FILTER_DISABLE; + break; + } + + /* If filter expression have been defined, match the + path name against the expression list. */ + + if (FilterList) { + struct CTM_Filter *filter; + + for (filter = FilterList; filter; + filter = filter->Next) { + if (0 == regexec(&filter->CompiledRegex, name, + 0, 0, 0)) + /* if the name matches, adopt the + action */ + match = filter->Action; + } + } + + /* Add up the total number of matches */ + total_matches += match; + break; + case CTM_F_Uid: + GETFIELD(p,sep); + while(*p) { + if(!isdigit(*p)) { + Fatal("Non-digit in uid."); + return 32; + } + p++; + } + break; + case CTM_F_Gid: + GETFIELD(p,sep); + while(*p) { + if(!isdigit(*p)) { + Fatal("Non-digit in gid."); + return 32; + } + p++; + } + break; + case CTM_F_Mode: + GETFIELD(p,sep); + while(*p) { + if(!isdigit(*p)) { + Fatal("Non-digit in mode."); + return 32; + } + p++; + } + break; + case CTM_F_MD5: + if(j & CTM_Q_MD5_Chunk) { + GETFIELDCOPY(md5,sep); /* XXX check for garbage */ + } else if(j & CTM_Q_MD5_Before) { + GETFIELD(p,sep); /* XXX check for garbage */ + } else if(j & CTM_Q_MD5_After) { + GETFIELD(p,sep); /* XXX check for garbage */ + } else { + fprintf(stderr,"List = 0x%x\n",j); + Fatal("Unqualified MD5."); + return 32; + } + break; + case CTM_F_Count: + GETBYTECNT(cnt,sep); + break; + case CTM_F_Bytes: + if(cnt < 0) WRONG + GETDATA(trash,cnt); + p = MD5Data(trash,cnt,md5_1); + if(md5 && strcmp(md5,p)) { + Fatal("Internal MD5 failed."); + return Exit_Garbage; + default: + fprintf(stderr,"List = 0x%x\n",j); + Fatal("List had garbage."); + return Exit_Garbage; + } + } + } + if(Verbose > 5) + putc('\n',stderr); + if(ListIt && match) + printf("> %s %s\n", sp->Key, name); + } + + Delete(md5); + Delete(name); + Delete(trash); + + q = MD5End (&ctx,md5_1); + if(Verbose > 2) + printf("Expecting Global MD5 <%s>\n",q); + GETFIELD(p,'\n'); /* <MD5> */ + if(Verbose > 2) + printf("Reference Global MD5 <%s>\n",p); + if(strcmp(q,p)) { + Fatal("MD5 sum doesn't match."); + fprintf(stderr,"\tI have:<%s>\n",q); + fprintf(stderr,"\tShould have been:<%s>\n",p); + return Exit_Garbage; + } + if (-1 != getc(fd)) { + if(!Force) { + Fatal("Trailing junk in CTM-file. Can Force with -F."); + return 16; + } + } + if ((Verbose > 1) && (0 == total_matches)) + printf("No matches in \"%s\"\n", FileName); + return (total_matches ? Exit_OK : Exit_NoMatch); +} diff --git a/usr.sbin/ctm/ctm/ctm_pass2.c b/usr.sbin/ctm/ctm/ctm_pass2.c new file mode 100644 index 0000000..a0b857a --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass2.c @@ -0,0 +1,301 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" +#define BADREAD 32 + +/*---------------------------------------------------------------------------*/ +/* Pass2 -- Validate the incoming CTM-file. + */ + +int +Pass2(FILE *fd) +{ + u_char *p,*q,*md5=0; + MD5_CTX ctx; + int i,j,sep,cnt,fdesc; + u_char *trash=0,*name=0; + struct CTM_Syntax *sp; + struct stat st; + int ret = 0; + int match = 0; + char md5_1[33]; + struct CTM_Filter *filter; + FILE *ed = NULL; + static char *template = NULL; + + if(Verbose>3) + printf("Pass2 -- Checking if CTM-patch will apply\n"); + MD5Init (&ctx); + + GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG + GETFIELD(p,' '); if(strcmp(Version,p)) WRONG + GETFIELD(p,' '); if(strcmp(Name,p)) WRONG + /* XXX Lookup name in /etc/ctm,conf, read stuff */ + GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG + /* XXX Verify that this is the next patch to apply */ + GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG + GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG + /* XXX drop or use ? */ + + for(;;) { + Delete(trash); + Delete(name); + Delete(md5); + cnt = -1; + + /* if a filter list was specified, check file name against + the filters specified + if no filter was given operate on all files. */ + match = (FilterList ? + !(FilterList->Action) : CTM_FILTER_ENABLE); + + GETFIELD(p,' '); + + if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG + + if(!strcmp(p+3,"_END")) + break; + + for(sp=Syntax;sp->Key;sp++) + if(!strcmp(p+3,sp->Key)) + goto found; + WRONG + found: + for(i=0;(j = sp->List[i]);i++) { + if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes) + sep = ' '; + else + sep = '\n'; + + switch (j & CTM_F_MASK) { + case CTM_F_Name: + GETNAMECOPY(name,sep,j,0); + /* If `keep' was specified, we won't remove any files, + so don't check if the file exists */ + if (KeepIt && + (!strcmp(sp->Key,"FR") || !strcmp(sp->Key,"DR"))) { + match = CTM_FILTER_DISABLE; + break; + } + + for (filter = FilterList; filter; filter = filter->Next) if (0 == regexec(&filter->CompiledRegex, name, + 0, 0, 0)) { + match = filter->Action; + } + + if (CTM_FILTER_DISABLE == match) + break; /* should ignore this file */ + + /* XXX Check DR DM rec's for parent-dir */ + if(j & CTM_Q_Name_New) { + /* XXX Check DR FR rec's for item */ + if(-1 != stat(name,&st)) { + fprintf(stderr," %s: %s exists.\n", + sp->Key,name); + ret |= Exit_Forcible; + } + break; + } + if(-1 == stat(name,&st)) { + fprintf(stderr," %s: %s doesn't exist.\n", + sp->Key,name); + if (sp->Key[1] == 'R') + ret |= Exit_Forcible; + else + ret |= Exit_NotOK; + break; + } + if (SetTime && getuid() && (getuid() != st.st_uid)) { + fprintf(stderr, + " %s: %s not mine, cannot set time.\n", + sp->Key,name); + ret |= Exit_NotOK; + } + if (j & CTM_Q_Name_Dir) { + if((st.st_mode & S_IFMT) != S_IFDIR) { + fprintf(stderr, + " %s: %s exist, but isn't dir.\n", + sp->Key,name); + ret |= Exit_NotOK; + } + break; + } + if (j & CTM_Q_Name_File) { + if((st.st_mode & S_IFMT) != S_IFREG) { + fprintf(stderr, + " %s: %s exist, but isn't file.\n", + sp->Key,name); + ret |= Exit_NotOK; + } + break; + } + break; + case CTM_F_Uid: + case CTM_F_Gid: + case CTM_F_Mode: + GETFIELD(p,sep); + break; + case CTM_F_MD5: + if(!name) WRONG + if(j & CTM_Q_MD5_Before) { + char *tmp; + GETFIELD(p,sep); + if(match && (st.st_mode & S_IFMT) == S_IFREG && + (tmp = MD5File(name,md5_1)) != NULL && + strcmp(tmp,p)) { + fprintf(stderr," %s: %s md5 mismatch.\n", + sp->Key,name); + GETFIELDCOPY(md5,sep); + if(md5 != NULL && strcmp(tmp,md5) == 0) { + fprintf(stderr," %s: %s already applied.\n", + sp->Key,name); + match = CTM_FILTER_DISABLE; + } else if(j & CTM_Q_MD5_Force) { + if(Force) + fprintf(stderr," Can and will force.\n"); + else + fprintf(stderr," Could have forced.\n"); + ret |= Exit_Forcible; + } else { + ret |= Exit_NotOK; + } + } + break; + } else if(j & CTM_Q_MD5_After) { + if(md5 == NULL) { + GETFIELDCOPY(md5,sep); + } + break; + } + /* Unqualified MD5 */ + WRONG + break; + case CTM_F_Count: + GETBYTECNT(cnt,sep); + break; + case CTM_F_Bytes: + if(cnt < 0) WRONG + GETDATA(trash,cnt); + if (!match) + break; + if (!template) { + if (asprintf(&template, "%s/CTMclientXXXXXX", + TmpDir) == -1) { + fprintf(stderr, " %s: malloc failed.\n", + sp->Key); + ret |= Exit_Mess; + return ret; + } + } + if(!strcmp(sp->Key,"FN")) { + if ((p = strdup(template)) == NULL) { + fprintf(stderr, " %s: malloc failed.\n", + sp->Key); + ret |= Exit_Mess; + return ret; + } + if ((fdesc = mkstemp(p)) == -1) { + fprintf(stderr, " %s: mkstemp failed.\n", + sp->Key); + ret |= Exit_Mess; + Free(p); + return ret; + } + if (close(fdesc) == -1) { + fprintf(stderr, " %s: close failed.\n", + sp->Key); + ret |= Exit_Mess; + unlink(p); + Free(p); + return ret; + } + j = ctm_edit(trash,cnt,name,p); + if(j) { + fprintf(stderr," %s: %s edit returned %d.\n", + sp->Key,name,j); + ret |= j; + unlink(p); + Free(p); + return ret; + } else if(strcmp(md5,MD5File(p,md5_1))) { + fprintf(stderr," %s: %s edit fails.\n", + sp->Key,name); + ret |= Exit_Mess; + unlink(p); + Free(p); + return ret; + } + unlink(p); + Free(p); + } else if (!strcmp(sp->Key,"FE")) { + if ((p = strdup(template)) == NULL) { + fprintf(stderr, " %s: malloc failed.\n", + sp->Key); + ret |= Exit_Mess; + return ret; + } + if ((fdesc = mkstemp(p)) == -1) { + fprintf(stderr, " %s: mkstemp failed.\n", + sp->Key); + ret |= Exit_Mess; + Free(p); + return ret; + } + if (close(fdesc) == -1) { + fprintf(stderr, " %s: close failed.\n", + sp->Key); + ret |= Exit_Mess; + unlink(p); + Free(p); + return ret; + } + ed = popen("ed","w"); + if (!ed) { + WRONG + } + fprintf(ed,"e %s\n", name); + if (cnt != fwrite(trash,1,cnt,ed)) { + warn("%s", name); + pclose(ed); + WRONG + } + fprintf(ed,"w %s\n",p); + if (pclose(ed)) { + warn("%s", p); + WRONG + } + if(strcmp(md5,MD5File(p,md5_1))) { + fprintf(stderr,"%s %s MD5 didn't come out right\n", + sp->Key, name); + WRONG + } + unlink(p); + Free(p); + } + + break; + default: WRONG + } + } + } + + Delete(trash); + Delete(name); + Delete(md5); + + q = MD5End (&ctx,md5_1); + GETFIELD(p,'\n'); /* <MD5> */ + if(strcmp(q,p)) WRONG + if (-1 != getc(fd)) WRONG + return ret; +} diff --git a/usr.sbin/ctm/ctm/ctm_pass3.c b/usr.sbin/ctm/ctm/ctm_pass3.c new file mode 100644 index 0000000..34c6d87c1d --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass3.c @@ -0,0 +1,295 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" +#define BADREAD 32 + +/*---------------------------------------------------------------------------*/ +/* Pass3 -- Validate the incoming CTM-file. + */ + +int +settime(const char *name, const struct timeval *times) +{ + if (SetTime) + if (utimes(name,times)) { + warn("utimes(): %s", name); + return -1; + } + return 0; +} + +int +Pass3(FILE *fd) +{ + u_char *p,*q,buf[BUFSIZ]; + MD5_CTX ctx; + int i,j,sep,cnt; + u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0; + struct CTM_Syntax *sp; + FILE *ed=0; + struct stat st; + char md5_1[33]; + int match=0; + struct timeval times[2]; + struct CTM_Filter *filter = NULL; + if(Verbose>3) + printf("Pass3 -- Applying the CTM-patch\n"); + MD5Init (&ctx); + + GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG + GETFIELD(p,' '); if(strcmp(Version,p)) WRONG + GETFIELD(p,' '); if(strcmp(Name,p)) WRONG + GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG + GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG + GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG + + /* + * This would be cleaner if mktime() worked in UTC rather than + * local time. + */ + if (SetTime) { + struct tm tm; + char *tz; + char buf[5]; + int i; + +#define SUBSTR(off,len) strncpy(buf, &TimeStamp[off], len), buf[len] = '\0' +#define WRONGDATE { fprintf(stderr, " %s failed date validation\n",\ + TimeStamp); WRONG} + + if (strlen(TimeStamp) != 15 || TimeStamp[14] != 'Z') WRONGDATE + for (i = 0; i < 14; i++) + if (!isdigit(TimeStamp[i])) WRONGDATE + + tz = getenv("TZ"); + if (setenv("TZ", "UTC", 1) < 0) WRONG + tzset(); + + tm.tm_isdst = tm.tm_gmtoff = 0; + + SUBSTR(0, 4); + tm.tm_year = atoi(buf) - 1900; + SUBSTR(4, 2); + tm.tm_mon = atoi(buf) - 1; + if (tm.tm_mon < 0 || tm.tm_mon > 11) WRONGDATE + SUBSTR(6, 2); + tm.tm_mday = atoi(buf); + if (tm.tm_mday < 1 || tm.tm_mday > 31) WRONG; + SUBSTR(8, 2); + tm.tm_hour = atoi(buf); + if (tm.tm_hour > 24) WRONGDATE + SUBSTR(10, 2); + tm.tm_min = atoi(buf); + if (tm.tm_min > 59) WRONGDATE + SUBSTR(12, 2); + tm.tm_sec = atoi(buf); + if (tm.tm_min > 62) WRONGDATE /* allow leap seconds */ + + times[0].tv_sec = times[1].tv_sec = mktime(&tm); + if (times[0].tv_sec == -1) WRONGDATE + times[0].tv_usec = times[1].tv_usec = 0; + + if (tz) { + if (setenv("TZ", tz, 1) < 0) WRONGDATE + } else { + unsetenv("TZ"); + } + } + + for(;;) { + Delete(md5); + Delete(uid); + Delete(gid); + Delete(mode); + Delete(md5before); + Delete(trash); + Delete(name); + cnt = -1; + + GETFIELD(p,' '); + + if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG + + if(!strcmp(p+3,"_END")) + break; + + for(sp=Syntax;sp->Key;sp++) + if(!strcmp(p+3,sp->Key)) + goto found; + WRONG + found: + for(i=0;(j = sp->List[i]);i++) { + if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes) + sep = ' '; + else + sep = '\n'; + + switch (j & CTM_F_MASK) { + case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break; + case CTM_F_Uid: GETFIELDCOPY(uid,sep); break; + case CTM_F_Gid: GETFIELDCOPY(gid,sep); break; + case CTM_F_Mode: GETFIELDCOPY(mode,sep); break; + case CTM_F_MD5: + if(j & CTM_Q_MD5_Before) + GETFIELDCOPY(md5before,sep); + else + GETFIELDCOPY(md5,sep); + break; + case CTM_F_Count: GETBYTECNT(cnt,sep); break; + case CTM_F_Bytes: GETDATA(trash,cnt); break; + default: WRONG + } + } + /* XXX This should go away. Disallow trailing '/' */ + j = strlen(name)-1; + if(name[j] == '/') name[j] = '\0'; + + /* + * If a filter list is specified, run thru the filter list and + * match `name' against filters. If the name matches, set the + * required action to that specified in the filter. + * The default action if no filterlist is given is to match + * everything. + */ + + match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE); + for (filter = FilterList; filter; filter = filter->Next) { + if (0 == regexec(&filter->CompiledRegex, name, + 0, 0, 0)) { + match = filter->Action; + } + } + + if (CTM_FILTER_DISABLE == match) /* skip file if disabled */ + continue; + + if (Verbose > 0) + fprintf(stderr,"> %s %s\n",sp->Key,name); + if(!strcmp(sp->Key,"FM") || !strcmp(sp->Key, "FS")) { + i = open(name,O_WRONLY|O_CREAT|O_TRUNC,0666); + if(i < 0) { + warn("%s", name); + WRONG + } + if(cnt != write(i,trash,cnt)) { + warn("%s", name); + WRONG + } + close(i); + if(strcmp(md5,MD5File(name,md5_1))) { + fprintf(stderr," %s %s MD5 didn't come out right\n", + sp->Key,name); + WRONG + } + if (settime(name,times)) WRONG + continue; + } + if(!strcmp(sp->Key,"FE")) { + ed = popen("ed","w"); + if(!ed) { + WRONG + } + fprintf(ed,"e %s\n",name); + if(cnt != fwrite(trash,1,cnt,ed)) { + warn("%s", name); + pclose(ed); + WRONG + } + fprintf(ed,"w %s\n",name); + if(pclose(ed)) { + warn("ed"); + WRONG + } + if(strcmp(md5,MD5File(name,md5_1))) { + fprintf(stderr," %s %s MD5 didn't come out right\n", + sp->Key,name); + WRONG + } + if (settime(name,times)) WRONG + continue; + } + if(!strcmp(sp->Key,"FN")) { + strcpy(buf,name); + strcat(buf,TMPSUFF); + i = ctm_edit(trash,cnt,name,buf); + if(i) { + fprintf(stderr," %s %s Edit failed with code %d.\n", + sp->Key,name,i); + WRONG + } + if(strcmp(md5,MD5File(buf,md5_1))) { + fprintf(stderr," %s %s Edit failed MD5 check.\n", + sp->Key,name); + WRONG + } + if (rename(buf,name) == -1) + WRONG + if (settime(name,times)) WRONG + continue; + } + if(!strcmp(sp->Key,"DM")) { + if(0 > mkdir(name,0777)) { + sprintf(buf,"mkdir -p %s",name); + system(buf); + } + if(0 > stat(name,&st) || ((st.st_mode & S_IFMT) != S_IFDIR)) { + fprintf(stderr,"<%s> mkdir failed\n",name); + WRONG + } + if (settime(name,times)) WRONG + continue; + } + if(!strcmp(sp->Key,"FR")) { + if (KeepIt) { + if (Verbose > 1) + printf("<%s> not removed\n", name); + } + else if (0 != unlink(name)) { + fprintf(stderr,"<%s> unlink failed\n",name); + if (!Force) + WRONG + } + continue; + } + if(!strcmp(sp->Key,"DR")) { + /* + * We cannot use rmdir() because we do not get the directories + * in '-depth' order (cvs-cur.0018.gz for examples) + */ + if (KeepIt) { + if (Verbose > 1) { + printf("<%s> not removed\n", name); + } + } else { + sprintf(buf,"rm -rf %s",name); + system(buf); + } + continue; + } + WRONG + } + + Delete(md5); + Delete(uid); + Delete(gid); + Delete(mode); + Delete(md5before); + Delete(trash); + Delete(name); + + q = MD5End (&ctx,md5_1); + GETFIELD(p,'\n'); + if(strcmp(q,p)) WRONG + if (-1 != getc(fd)) WRONG + return 0; +} diff --git a/usr.sbin/ctm/ctm/ctm_passb.c b/usr.sbin/ctm/ctm/ctm_passb.c new file mode 100644 index 0000000..ee3a69c5f --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_passb.c @@ -0,0 +1,142 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <koshy@india.hp.com> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Joseph Koshy + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" +#define BADREAD 32 + +/*---------------------------------------------------------------------------*/ +/* PassB -- Backup modified files. + */ + +int +PassB(FILE *fd) +{ + u_char *p,*q; + MD5_CTX ctx; + int i,j,sep,cnt; + u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0; + struct CTM_Syntax *sp; + FILE *b = 0; /* backup command */ + u_char buf[BUFSIZ]; + char md5_1[33]; + int ret = 0; + int match = 0; + struct CTM_Filter *filter = NULL; + + if(Verbose>3) + printf("PassB -- Backing up files which would be changed.\n"); + + MD5Init (&ctx); + snprintf(buf, sizeof(buf), fmtcheck(TarCmd, TARCMD), BackupFile); + b=popen(buf, "w"); + if(!b) { warn("%s", buf); return Exit_Garbage; } + + GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG + GETFIELD(p,' '); if(strcmp(Version,p)) WRONG + GETFIELD(p,' '); if(strcmp(Name,p)) WRONG + GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG + GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG + GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG + + for(;;) { + Delete(md5); + Delete(uid); + Delete(gid); + Delete(mode); + Delete(md5before); + Delete(trash); + Delete(name); + cnt = -1; + + GETFIELD(p,' '); + + if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG + + if(!strcmp(p+3,"_END")) + break; + + for(sp=Syntax;sp->Key;sp++) + if(!strcmp(p+3,sp->Key)) + goto found; + WRONG + found: + for(i=0;(j = sp->List[i]);i++) { + if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes) + sep = ' '; + else + sep = '\n'; + + switch (j & CTM_F_MASK) { + case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break; + case CTM_F_Uid: GETFIELDCOPY(uid,sep); break; + case CTM_F_Gid: GETFIELDCOPY(gid,sep); break; + case CTM_F_Mode: GETFIELDCOPY(mode,sep); break; + case CTM_F_MD5: + if(j & CTM_Q_MD5_Before) + GETFIELDCOPY(md5before,sep); + else + GETFIELDCOPY(md5,sep); + break; + case CTM_F_Count: GETBYTECNT(cnt,sep); break; + case CTM_F_Bytes: GETDATA(trash,cnt); break; + default: WRONG + } + } + /* XXX This should go away. Disallow trailing '/' */ + j = strlen(name)-1; + if(name[j] == '/') name[j] = '\0'; + + if (KeepIt && + (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR"))) + continue; + + /* match the name against the elements of the filter list. The + action associated with the last matched filter determines whether + this file should be ignored or backed up. */ + match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE); + for (filter = FilterList; filter; filter = filter->Next) { + if (0 == regexec(&filter->CompiledRegex, name, 0, 0, 0)) + match = filter->Action; + } + + if (CTM_FILTER_DISABLE == match) + continue; + + if (!strcmp(sp->Key,"FS") || !strcmp(sp->Key,"FN") || + !strcmp(sp->Key,"AS") || !strcmp(sp->Key,"DR") || + !strcmp(sp->Key,"FR")) { + /* send name to the archiver for a backup */ + cnt = strlen(name); + if (cnt != fwrite(name,1,cnt,b) || EOF == fputc('\n',b)) { + warn("%s", name); + pclose(b); + WRONG; + } + } + } + + ret = pclose(b); + + Delete(md5); + Delete(uid); + Delete(gid); + Delete(mode); + Delete(md5before); + Delete(trash); + Delete(name); + + q = MD5End (&ctx,md5_1); + GETFIELD(p,'\n'); /* <MD5> */ + if(strcmp(q,p)) WRONG + if (-1 != getc(fd)) WRONG + return ret; +} diff --git a/usr.sbin/ctm/ctm/ctm_syntax.c b/usr.sbin/ctm/ctm/ctm_syntax.c new file mode 100644 index 0000000..6638943 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_syntax.c @@ -0,0 +1,67 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + */ + +#include "ctm.h" + +/* The fields... */ +#define Name CTM_F_Name +#define Uid CTM_F_Uid +#define Gid CTM_F_Gid +#define Mode CTM_F_Mode +#define MD5 CTM_F_MD5 +#define Count CTM_F_Count +#define Bytes CTM_F_Bytes + +/* The qualifiers... */ +#define File CTM_Q_Name_File +#define Dir CTM_Q_Name_Dir +#define New CTM_Q_Name_New +#define Subst CTM_Q_Name_Subst +#define After CTM_Q_MD5_After +#define Before CTM_Q_MD5_Before +#define Chunk CTM_Q_MD5_Chunk +#define Force CTM_Q_MD5_Force + +static int ctmFM[] = /* File Make */ + { Name|File|New|Subst, Uid, Gid, Mode, + MD5|After|Chunk, Count, Bytes,0 }; + +static int ctmFS[] = /* File Substitute */ + { Name|File|Subst, Uid, Gid, Mode, + MD5|Before|Force, MD5|After|Chunk, Count, Bytes,0 }; + +static int ctmFE[] = /* File Edit */ + { Name|File|Subst, Uid, Gid, Mode, + MD5|Before, MD5|After, Count, Bytes,0 }; + +static int ctmFR[] = /* File Remove */ + { Name|File|Subst, MD5|Before, 0 }; + +static int ctmAS[] = /* Attribute Substitute */ + { Name|Subst, Uid, Gid, Mode, 0 }; + +static int ctmDM[] = /* Directory Make */ + { Name|Dir|New , Uid, Gid, Mode, 0 }; + +static int ctmDR[] = /* Directory Remove */ + { Name|Dir, 0 }; + +struct CTM_Syntax Syntax[] = { + { "FM", ctmFM }, + { "FS", ctmFS }, + { "FE", ctmFE }, + { "FN", ctmFE }, + { "FR", ctmFR }, + { "AS", ctmAS }, + { "DM", ctmDM }, + { "DR", ctmDR }, + { 0, 0} }; diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile b/usr.sbin/ctm/ctm_dequeue/Makefile new file mode 100644 index 0000000..d2f5648 --- /dev/null +++ b/usr.sbin/ctm/ctm_dequeue/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../ctm_rmail + +PROG= ctm_dequeue +MAN= +SRCS= ctm_dequeue.c error.c + +CFLAGS+= -I${.CURDIR}/../ctm_rmail + +WARNS?= 1 + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile.depend b/usr.sbin/ctm/ctm_dequeue/Makefile.depend new file mode 100644 index 0000000..3646e2e --- /dev/null +++ b/usr.sbin/ctm/ctm_dequeue/Makefile.depend @@ -0,0 +1,18 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c new file mode 100644 index 0000000..2dd6e86 --- /dev/null +++ b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 1996, Gary J. Palmer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * verbatim and that no modifications are made prior to this + * point in the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * ctm_dequeue: Dequeue queued delta pieces and mail them. + * + * The pieces have already been packaged up as mail messages by ctm_smail, + * and will be simply passed to sendmail in alphabetical order. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fts.h> +#include <limits.h> +#include <errno.h> +#include <paths.h> +#include "error.h" +#include "options.h" + +#define DEFAULT_NUM 1 /* Default number of pieces mailed per run. */ + +int fts_sort(const FTSENT * const *, const FTSENT * const *); +int run_sendmail(int ifd); + +int +main(int argc, char **argv) +{ + char *log_file = NULL; + char *queue_dir = NULL; + char *list[2]; + int num_to_send = DEFAULT_NUM, chunk; + int fd; + FTS *fts; + FTSENT *ftsent; + int piece, npieces; + char filename[PATH_MAX]; + + err_prog_name(argv[0]); + + OPTIONS("[-l log] [-n num] queuedir") + NUMBER('n', num_to_send) + STRING('l', log_file) + ENDOPTS + + if (argc != 2) + usage(); + + if (log_file) + err_set_log(log_file); + + queue_dir = argv[1]; + list[0] = queue_dir; + list[1] = NULL; + + fts = fts_open(list, FTS_PHYSICAL|FTS_COMFOLLOW, fts_sort); + if (fts == NULL) + { + err("fts failed on `%s'", queue_dir); + exit(1); + } + + ftsent = fts_read(fts); + if (ftsent == NULL || ftsent->fts_info != FTS_D) + { + err("not a directory: %s", queue_dir); + exit(1); + } + + ftsent = fts_children(fts, 0); + if (ftsent == NULL && errno) + { + err("*ftschildren failed"); + exit(1); + } + + for (chunk = 1; ftsent != NULL; ftsent = ftsent->fts_link) + { + /* + * Skip non-files and ctm_smail tmp files (ones starting with `.') + */ + if (ftsent->fts_info != FTS_F || ftsent->fts_name[0] == '.') + continue; + + sprintf(filename, "%s/%s", queue_dir, ftsent->fts_name); + fd = open(filename, O_RDONLY); + if (fd < 0) + { + err("*open: %s", filename); + exit(1); + } + + if (run_sendmail(fd)) + exit(1); + + close(fd); + + if (unlink(filename) < 0) + { + err("*unlink: %s", filename); + exit(1); + } + + /* + * Deduce the delta, piece number, and number of pieces from + * the name of the file in the queue. Ideally, we should be + * able to get the mail alias name too. + * + * NOTE: This depends intimately on the queue name used in ctm_smail. + */ + npieces = atoi(&ftsent->fts_name[ftsent->fts_namelen-3]); + piece = atoi(&ftsent->fts_name[ftsent->fts_namelen-7]); + err("%.*s %d/%d sent", (int)(ftsent->fts_namelen-8), ftsent->fts_name, + piece, npieces); + + if (chunk++ == num_to_send) + break; + } + + fts_close(fts); + + return(0); +} + +int +fts_sort(const FTSENT * const * a, const FTSENT * const * b) +{ + if ((*a)->fts_info != FTS_F) + return(0); + if ((*a)->fts_info != FTS_F) + return(0); + + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + +/* + * Run sendmail with the given file descriptor as standard input. + * Sendmail will decode the destination from the message contents. + * Returns 0 on success, 1 on failure. + */ +int +run_sendmail(int ifd) +{ + pid_t child, pid; + int status; + + switch (child = fork()) + { + case -1: + err("*fork"); + return(1); + + case 0: /* Child */ + dup2(ifd, 0); + execl(_PATH_SENDMAIL, _PATH_SENDMAIL, "-odq", "-t", (char *)NULL); + err("*exec: %s", _PATH_SENDMAIL); + _exit(1); + + default: /* Parent */ + while ((pid = wait(&status)) != child) + { + if (pid == -1 && errno != EINTR) + { + err("*wait"); + return(1); + } + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + err("sendmail failed"); + return(1); + } + } + + return(0); +} diff --git a/usr.sbin/ctm/ctm_rmail/Makefile b/usr.sbin/ctm/ctm_rmail/Makefile new file mode 100644 index 0000000..1278ab4 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= ctm_rmail +MLINKS= ctm_rmail.1 ctm_smail.1 \ + ctm_rmail.1 ctm_dequeue.1 +SRCS= ctm_rmail.c error.c + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_rmail/Makefile.depend b/usr.sbin/ctm/ctm_rmail/Makefile.depend new file mode 100644 index 0000000..3646e2e --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/Makefile.depend @@ -0,0 +1,18 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif 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..232f646 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 @@ -0,0 +1,510 @@ +.\" 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 +.\" +.\" $FreeBSD$ +.\" +.Dd January 24, 1995 +.Dt CTM_MAIL 1 +.Os +.Sh NAME +.Nm ctm_smail , +.Nm ctm_dequeue , +.Nm ctm_rmail +.Nd send and receive +.Xr ctm 1 +deltas via mail +.Sh SYNOPSIS +.Nm ctm_smail +.Op Fl l Ar log +.Op Fl m Ar maxmsgsize +.Op Fl c Ar maxctmsize +.Op Fl q Ar queue-dir +.Ar ctm-delta +.Ar mail-alias +.Nm ctm_dequeue +.Op Fl l Ar log +.Op Fl n Ar numchunks +.Ar queue-dir +.Nm ctm_rmail +.Op Fl Dfuv +.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 conjunction with the +.Xr ctm 1 +command, +.Nm ctm_smail , +.Nm ctm_dequeue +and +.Nm ctm_rmail +are used to distribute changes to a source tree via email. +The +.Nm ctm_smail +utility is given a compressed +.Xr 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 +(optionally queued to spread the mail load). +Each recipient uses +.Nm ctm_rmail +(either manually or automatically) to decode and reassemble the delta, and +optionally call +.Xr ctm +to apply it to the source tree. +At the moment, +several source trees are distributed, and by several sites. +These include +the +.Fx Ns -current +source and CVS trees, distributed by +.Li freefall.FreeBSD.org . +.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. +.It Fl q Ar queue-dir +Instead of mailing the delta pieces now, store them in the given directory +to be mailed later using +.Nm ctm_dequeue . +This feature allows the mailing of large deltas to be spread out over +hours or even days to limit the impact on recipients with limited network +bandwidth or small mail spool areas. +.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_dequeue : +.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 n Ar numchunks +Limit the number of mail messages that +.Nm ctm_dequeue +will send per run. +By default, +.Nm ctm_dequeue +will send one mail message per run. +.El +.Pp +.Ar queuedir +is the directory containing the mail messages stored by +.Nm ctm_smail . +Up to +.Ar numchunks +mail messages will be sent in each run. +The recipient mailing list is already +encoded in the queued files. +.Pp +It is safe to run +.Nm ctm_dequeue +while +.Nm ctm_smail +is adding entries to the queue, or even to run +.Nm ctm_smail +multiple times concurrently, but a separate queue directory should be used +for each tree being distributed. +This is because entries are served in +alphabetical order, and one tree will be unfairly serviced before any others, +based on the delta names, not delta creation times. +.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 +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 . +It is probably a good idea to avoid this flag (and keep all the deltas) as +.Xr ctm +has the ability to recover small groups of files from a full set of deltas. +.It Fl f +Fork and execute in the background while applying deltas with +.Xr ctm . +This is useful when automatically invoking +.Nm ctm_rmail +from +.Xr sendmail +because +.Xr ctm +can take a very long time to complete, causing other people's mail to +be delayed, and can in theory cause spurious +mail retransmission due to the remote +.Xr sendmail +timing out, or even termination of +.Nm ctm_rmail +by mail filters such as +.Xr "MH's" +.Xr slocal . +Do not worry about zillions of background +.Xr ctm +processes loading your machine, since locking is used to prevent more than one +.Xr ctm +invocation at a time. +.It Fl u +Pass the +.Fl u +flag to the +.Xr ctm +command when applying the complete deltas, causing it to set the modification +time of created and modified files to the CTM delta creation time. +.It Fl v +Pass the +.Fl v +flag to the +.Xr ctm +command when applying the complete deltas, causing a more informative +output. +All +.Xr ctm +output appears in the +.Nm ctm_rmail +log file. +.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. +.Pp +It is safe to invoke +.Nm ctm_rmail +multiple times concurrently (with different input files), +as might happen when +.Xr sendmail +is delivering mail asynchronously. +This is because locking is used to +keep things orderly. +.Sh FILE FORMAT +Following are the important parts of an actual (very small) delta piece: +.Bd -literal +From: owner-src-cur +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 are 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: owner-src-cur +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 ftp. +.Ed +.Pp +You are then on your own! +.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 QUEUEDIR/* +Pieces of deltas encoded as mail messages waiting to be sent to the +mailing list. +.It Pa PIECEDIR/* +Pieces of deltas waiting for the rest to arrive. +.It Pa DELTADIR/* +Completed deltas. +.It Pa BASEDIR/.ctm_status +File containing the name and number of the next delta to be applied to this +source tree. +.El +.Sh EXIT STATUS +The +.Nm ctm_smail , +.Nm ctm_dequeue +and +.Nm ctm_rmail +utilities return exit status 0 for success, and 1 for various failures. +The +.Nm ctm_rmail +utility 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 +.Xr ctm +is not considered an error important enough to bounce the mail, and +.Nm ctm_rmail +returns an exit status of 0. +.Sh EXAMPLES +To send delta 32 of +.Em src-cur +to a group of wonderful code hackers known to +.Xr sendmail +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/mail/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 -d /ctm/deltas -b /ctm/src-cur -l /ctm/apply.log +.Ed +.Pp +For maximum flexibility, consider this excerpt from a +.Xr procmail +script: +.Bd -literal -offset indent +PATH=$HOME/bin:$PATH + +:0 w +* ^Subject: ctm-mail cvs-cur +| ctm_incoming +.Ed +.Pp +together with the +shell script +.Pa ~/bin/ctm_incoming : +.Bd -literal -offset indent +#! /bin/sh +PATH="$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin" +export PATH + +cd $HOME/ctm && ctm_rmail -f -p pieces -d deltas -l log -b /ctm +.Ed +.Pp +which will deposit all +.Xr ctm +deltas in +.Pa ~/ctm/deltas , +apply them to the tree in +.Pa /ctm , +and drop any failures into your regular mail box. +Note the +.Ev PATH +manipulation in +.Pa ctm_incoming +which allows +.Nm ctm_rmail +to execute +.Xr ctm 1 +on the +.Pq non- Ns Fx +machine that this example was taken from. +.Sh SECURITY +On its own, CTM is an insecure protocol +- there is no authentication performed that the +changes applied to the source code were sent by a +trusted party, and so care should be taken if the +CTM deltas are obtained via an unauthenticated +medium such as regular email. +It is a relatively simple matter for an attacker +to forge a CTM delta to replace or precede the +legitimate one and insert malicious code into your +source tree. +If the legitimate delta is somehow prevented from +arriving, this will go unnoticed until a later +delta attempts to touch the same file, at which +point the MD5 checksum will fail. +.Pp +To remedy this insecurity, CTM delta pieces generated by +FreeBSD.org are cryptographically signed in a +format compatible with the GNU Privacy Guard +utility, available in /usr/ports/security/gpg, and +the Pretty Good Privacy v5 utility, +/usr/ports/security/pgp5. +The relevant public key can be obtained by +fingering ctm@FreeBSD.org. +.Pp +CTM deltas which are thus signed cannot be +undetectably altered by an attacker. +Therefore it is recommended that you make use of +GPG or PGP5 to verify the signatures if you +receive your CTM deltas via email. +.Sh DIAGNOSTICS +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 +or, if queueing, +.Bd -literal -offset indent +ctm_smail: src-cur.0250.gz 1/2 queued for src-guys +.Ed +.Pp +The +.Nm ctm_dequeue +utility will report messages like: +.Bd -literal -offset indent +ctm_dequeue: src-cur.0250.gz 1/2 sent +.Ed +.Pp +The +.Nm ctm_rmail +utility 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 +If any of the input files do not contain a valid delta piece, +.Nm ctm_rmail +will report: +.Bd -literal -offset indent +ctm_rmail: message contains no delta +.Ed +.Pp +and return an exit status of 1. +You can use this to redirect wayward messages +back into your real mailbox if your mail filter goes wonky. +.Pp +These messages go to +.Em stderr +or to the log file. +Messages from +.Xr ctm 1 +turn up here too. +Error messages should be self explanatory. +.Sh SEE ALSO +.Xr ctm 1 , +.Xr ctm 5 +.\" .Sh HISTORY +.Sh AUTHORS +.An Stephen McKay Aq Mt mckay@FreeBSD.org 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..a46a58aa --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c @@ -0,0 +1,672 @@ +/* + * 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. + * + * $FreeBSD$ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.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. */ +int apply_verbose = 0; /* Run with '-v' */ +int set_time = 0; /* Set the time of the files that is changed. */ +int mask = 0; /* The current umask */ + +void apply_complete(void); +int read_piece(char *input_file); +int combine_if_complete(char *delta, int pce, int npieces); +int combine(char *delta, int npieces, char *dname, char *pname, char *tname); +int decode_line(char *line, char *out_buf); +int lock_file(char *name); + +/* + * 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'. + */ +int +main(int argc, char **argv) + { + char *log_file = NULL; + int status = 0; + int fork_ctm = 0; + + mask = umask(0); + umask(mask); + + err_prog_name(argv[0]); + + OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]") + FLAG('D', delete_after) + FLAG('f', fork_ctm) + FLAG('u', set_time) + FLAG('v', apply_verbose) + STRING('p', piece_dir) + STRING('d', delta_dir) + STRING('b', base_dir) + STRING('l', log_file) + ENDOPTS + + if (delta_dir == NULL) + usage(); + + if (piece_dir == NULL && (base_dir == NULL || argc > 1)) + usage(); + + if (log_file != NULL) + err_set_log(log_file); + + /* + * Digest each file in turn, or just stdin if no files were given. + */ + if (argc <= 1) + { + if (piece_dir != NULL) + status = read_piece(NULL); + } + else + { + while (*++argv != NULL) + status |= read_piece(*argv); + } + + /* + * Maybe it's time to look for and apply completed deltas with ctm. + * + * Shall we report back to sendmail immediately, and let a child do + * the work? Sendmail will be waiting for us to complete, delaying + * other mail, and possibly some intermediate process (like MH slocal) + * will terminate us if we take too long! + * + * If fork() fails, it's unlikely we'll be able to run ctm, so give up. + * Also, the child exit status is unimportant. + */ + if (base_dir != NULL) + if (!fork_ctm || fork() == 0) + 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+%03d-%03d", 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; + int lfd; + FILE *fp, *ctm; + struct stat sb; + char class[20]; + char delta[30]; + char junk[2]; + char fname[PATH_MAX]; + char here[PATH_MAX]; + char buf[PATH_MAX*2]; + + /* + * Grab a lock on the ctm mutex file so that we can be sure we are + * working alone, not fighting another ctm_rmail! + */ + strcpy(fname, delta_dir); + strcat(fname, "/.mutex_apply"); + if ((lfd = lock_file(fname)) < 0) + return; + + /* + * Find out which delta ctm needs next. + */ + sprintf(fname, "%s/%s", base_dir, CTM_STATUS); + if ((fp = fopen(fname, "r")) == NULL) + { + close(lfd); + return; + } + + i = fscanf(fp, "%19s %d %c", class, &dn, junk); + fclose(fp); + if (i != 2) + { + close(lfd); + 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) + break; + + sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir, + set_time ? "-u " : "", + apply_verbose ? "-v " : "", here, fname); + if ((ctm = popen(buf, "r")) == NULL) + { + err("ctm failed to apply %s", delta); + break; + } + + 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); + break; + } + + if (delete_after) + unlink(fname); + + err("%s applied%s", delta, delete_after ? " and deleted" : ""); + } + + /* + * Closing the lock file clears the lock. + */ + close(lfd); + } + + +/* + * 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 = 0; + int decoding = 0; + int got_one = 0; + int line_no = 0; + int i, n; + int pce, npieces; + unsigned claimed_cksum; + unsigned short cksum = 0; + char out_buf[200]; + char line[200]; + char delta[30]; + char pname[PATH_MAX]; + char tname[PATH_MAX]; + 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++; + + /* + * Remove all trailing white space. + */ + i = strlen(line) - 1; + while (i > 0 && isspace(line[i])) + line[i--] = '\0'; + + /* + * Look for the beginning of an encoded piece. + */ + if (!decoding) + { + char *s; + int fd = -1; + + if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c", + delta, &pce, &npieces, junk) != 3) + continue; + + while ((s = strchr(delta, '/')) != NULL) + *s = '_'; + + got_one++; + strcpy(tname, piece_dir); + strcat(tname, "/p.XXXXXXXXXX"); + if ((fd = mkstemp(tname)) == -1 || + (ofp = fdopen(fd, "w")) == NULL) + { + if (fd != -1) { + err("cannot open '%s' for writing", tname); + close(fd); + } + else + err("*mkstemp: '%s'", tname); + 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", tname); + + 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(tname); + status++; + continue; + } + + mk_piece_name(pname, delta, pce, npieces); + if (rename(tname, pname) < 0) + { + err("*rename: '%s' to '%s'", tname, pname); + err("%s %d/%d lost!", delta, pce, npieces); + unlink(tname); + 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(tname); + + 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(tname); + + status++; + } + + if (ferror(ifp)) + { + err("error reading %s", input_file == NULL ? "stdin" : input_file); + status++; + } + + if (input_file != NULL) + fclose(ifp); + + if (!got_one) + { + err("message contains no delta"); + status++; + } + + 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, e; + int lfd; + struct stat sb; + char pname[PATH_MAX]; + char dname[PATH_MAX]; + char tname[PATH_MAX]; + + /* + * We can probably just rename() it into place if it is a small delta. + */ + if (npieces == 1) + { + mk_delta_name(dname, delta); + mk_piece_name(pname, delta, 1, 1); + if (rename(pname, dname) == 0) + { + chmod(dname, 0666 & ~mask); + err("%s complete", delta); + return 1; + } + } + + /* + * Grab a lock on the reassembly mutex file so that we can be sure we are + * working alone, not fighting another ctm_rmail! + */ + strcpy(tname, delta_dir); + strcat(tname, "/.mutex_build"); + if ((lfd = lock_file(tname)) < 0) + return 0; + + /* + * Are all of the pieces present? Of course the current one is, + * unless all pieces are missing because another ctm_rmail has + * processed them already. + */ + for (i = 1; i <= npieces; i++) + { + if (i == pce) + continue; + mk_piece_name(pname, delta, i, npieces); + if (stat(pname, &sb) < 0) + { + close(lfd); + return 1; + } + } + + /* + * Stick them together. Let combine() use our file name buffers, since + * we're such good buddies. :-) + */ + e = combine(delta, npieces, dname, pname, tname); + close(lfd); + return e; + } + + +/* + * Put the pieces together to form a delta. + * Returns 1 on success, and 0 on failure. + * Note: dname, pname, and tname are room for some file names that just + * happened to by lying around in the calling routine. Waste not, want not! + */ +int +combine(char *delta, int npieces, char *dname, char *pname, char *tname) + { + FILE *dfp, *pfp; + int i, n, e; + char buf[BUFSIZ]; + int fd = -1; + + strcpy(tname, delta_dir); + strcat(tname, "/d.XXXXXXXXXX"); + if ((fd = mkstemp(tname)) == -1 || + (dfp = fdopen(fd, "w")) == NULL) + { + if (fd != -1) { + close(fd); + err("cannot open '%s' for writing", tname); + } + else + err("*mkstemp: '%s'", tname); + return 0; + } + + /* + * 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(tname); + return 0; + } + while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0) + fwrite(buf, sizeof(char), n, dfp); + e = ferror(pfp); + fclose(pfp); + if (e) + { + err("error reading '%s'", pname); + fclose(dfp); + unlink(tname); + return 0; + } + } + fflush(dfp); + e = ferror(dfp); + fclose(dfp); + if (e) + { + err("error writing '%s'", tname); + unlink(tname); + return 0; + } + + mk_delta_name(dname, delta); + if (rename(tname, dname) < 0) + { + err("*rename: '%s' to '%s'", tname, dname); + unlink(tname); + return 0; + } + chmod(dname, 0666 & ~mask); + + /* + * Throw the pieces away. + */ + for (i = 1; i <= npieces; i++) + { + mk_piece_name(pname, delta, i, npieces); + if (unlink(pname) < 0) + err("*unlink: '%s'", 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); + } + + +/* + * Create and lock the given file. + * + * Clearing the lock is as simple as closing the file descriptor we return. + */ +int +lock_file(char *name) + { + int lfd; + + if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0) + { + err("*open: '%s'", name); + return -1; + } + if (flock(lfd, LOCK_EX) < 0) + { + close(lfd); + err("*flock: '%s'", name); + return -1; + } + return lfd; + } diff --git a/usr.sbin/ctm/ctm_rmail/error.c b/usr.sbin/ctm/ctm_rmail/error.c new file mode 100644 index 0000000..56d3dc6 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/error.c @@ -0,0 +1,102 @@ +/* + * Routines for logging error messages or other informative messages. + * + * Log messages can easily contain the program name, a time stamp, system + * error messages, and arbitrary printf-style strings, and can be directed + * to stderr or a log file. + * + * 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. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <errno.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. + * + * A leading '*' in the message format means we want the system errno + * decoded and appended. + */ +void +err(const char *fmt, ...) + { + va_list ap; + time_t now; + struct tm *tm; + FILE *fp; + int x = errno; + int want_errno; + + 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); + } + + want_errno = 0; + if (*fmt == '*') + want_errno++, fmt++; + + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); + + if (want_errno) + fprintf(fp, ": %s", strerror(x)); + + 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..c631b67 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/error.h @@ -0,0 +1,5 @@ +/* $FreeBSD$ */ + +extern void err_set_log(char *log_file); +extern void err_prog_name(char *name); +extern void err(const char *fmt, ...) __printflike(1, 2); diff --git a/usr.sbin/ctm/ctm_rmail/options.h b/usr.sbin/ctm/ctm_rmail/options.h new file mode 100644 index 0000000..86c1247 --- /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). + * + * $FreeBSD$ + */ + +static char *O_usage; +static char *O_name; +extern long atol(); + +void +pusage() + { + /* + * Avoid gratuitously loading stdio. + */ + write(STDERR_FILENO, "usage: ", 7); + write(STDERR_FILENO, O_name, strlen(O_name)); + write(STDERR_FILENO, " ", 1); + write(STDERR_FILENO, O_usage, strlen(O_usage)); + write(STDERR_FILENO, "\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) \ + { \ + default: \ + 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; \ + } \ + } \ + *--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..4c98d31 --- /dev/null +++ b/usr.sbin/ctm/ctm_smail/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../ctm_rmail + +PROG= ctm_smail +MAN= +SRCS= ctm_smail.c error.c + +CFLAGS+= -I${.CURDIR}/../ctm_rmail + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_smail/Makefile.depend b/usr.sbin/ctm/ctm_smail/Makefile.depend new file mode 100644 index 0000000..3646e2e --- /dev/null +++ b/usr.sbin/ctm/ctm_smail/Makefile.depend @@ -0,0 +1,18 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif 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..c90fa2f --- /dev/null +++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c @@ -0,0 +1,497 @@ +/* + * Send a compressed CTM delta to a recipient mailing list by encoding it + * in safe ASCII characters, in mailer-friendly chunks, and passing them + * to sendmail. Optionally, the chunks can be queued to be sent later by + * ctm_dequeue in controlled bursts. 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. + * + * $FreeBSD$ + */ + +#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 <limits.h> +#include "error.h" +#include "options.h" + +#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ + +#define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */ + +int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, + long max_msg_size, char *mail_alias, char *queue_dir); +int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, + char *mail_alias); +int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, + char *mail_alias, char *queue_dir); +void clean_up_queue(char *queue_dir); +int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum); +void write_header(FILE *sfp, char *mail_alias, char *delta, int pce, + int npieces); +void write_trailer(FILE *sfp, unsigned sum); +int apologise(char *delta, off_t ctm_size, long max_ctm_size, + char *mail_alias, char *queue_dir); +FILE *open_sendmail(void); +int close_sendmail(FILE *fp); + +int +main(int argc, char **argv) + { + int status = 0; + 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; + char *delta; + FILE *dfp; + 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 ((delta = strrchr(delta_file, '/')) == NULL) + delta = delta_file; + else + delta++; + + if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0) + { + err("*%s", delta_file); + exit(1); + } + + if (max_ctm_size != 0 && sb.st_size > max_ctm_size) + status = apologise(delta, sb.st_size, max_ctm_size, mail_alias, + queue_dir); + else + status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size, + mail_alias, queue_dir); + + fclose(dfp); + + return status; + } + + +/* + * Carve our CTM delta into pieces, encode them, and send or queue them. + * Returns 0 on success, and 1 on failure. + */ +int +chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size, + long max_msg_size, char *mail_alias, char *queue_dir) + { + int npieces; + long msg_size; + long exp_size; + int status; + +#undef howmany +#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 (queue_dir == NULL) + status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias); + else + { + status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias, + queue_dir); + if (status) + clean_up_queue(queue_dir); + } + + return status; + } + + +/* + * Carve our CTM delta into pieces, encode them, and send them. + * Returns 0 on success, and 1 on failure. + */ +int +chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces, + char *mail_alias) + { + int pce; + FILE *sfp; + unsigned sum; + + /* + * Send each chunk directly to sendmail as it is generated. + * No temporary files necessary. If things turn ugly, we just + * have to live with the fact the we have sent only part of + * the delta. + */ + for (pce = 1; pce <= npieces; pce++) + { + int read_error; + + if ((sfp = open_sendmail()) == NULL) + return 1; + + write_header(sfp, mail_alias, delta, pce, npieces); + read_error = encode_body(sfp, dfp, msg_size, &sum); + if (!read_error) + write_trailer(sfp, sum); + + if (!close_sendmail(sfp) || read_error) + return 1; + + err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); + } + + return 0; + } + + +/* + * Construct the tmp queue file name of a delta piece. + */ +#define mk_tmp_name(fn,qd,p) \ + sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p)) + +/* + * Construct the final queue file name of a delta piece. + */ +#define mk_queue_name(fn,qd,d,p,n) \ + sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n)) + +/* + * Carve our CTM delta into pieces, encode them, and queue them. + * Returns 0 on success, and 1 on failure. + */ +int +chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces, + char *mail_alias, char *queue_dir) + { + int pce; + FILE *qfp; + unsigned sum; + char tname[PATH_MAX]; + char qname[PATH_MAX]; + + /* + * Store each piece in the queue directory, but under temporary names, + * so that they can be deleted without unpleasant consequences if + * anything goes wrong. We could easily fill up a disk, for example. + */ + for (pce = 1; pce <= npieces; pce++) + { + int write_error; + + mk_tmp_name(tname, queue_dir, pce); + if ((qfp = fopen(tname, "w")) == NULL) + { + err("cannot open '%s' for writing", tname); + return 1; + } + + write_header(qfp, mail_alias, delta, pce, npieces); + if (encode_body(qfp, dfp, msg_size, &sum)) + return 1; + write_trailer(qfp, sum); + + fflush(qfp); + write_error = ferror(qfp); + fclose(qfp); + if (write_error) + { + err("error writing '%s'", tname); + return 1; + } + + /* + * Give the warm success message now, instead of all in a rush + * during the rename phase. + */ + err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias); + } + + /* + * Rename the pieces into place. If an error occurs now, we are + * stuffed, but there is no neat way to back out. rename() should + * only fail now under extreme circumstances. + */ + for (pce = 1; pce <= npieces; pce++) + { + mk_tmp_name(tname, queue_dir, pce); + mk_queue_name(qname, queue_dir, delta, pce, npieces); + if (rename(tname, qname) < 0) + { + err("*rename: '%s' to '%s'", tname, qname); + unlink(tname); + } + } + + return 0; + } + + +/* + * There may be temporary files cluttering up the queue directory. + */ +void +clean_up_queue(char *queue_dir) + { + int pce; + char tname[PATH_MAX]; + + err("discarding queued delta pieces"); + for (pce = 1; ; pce++) + { + mk_tmp_name(tname, queue_dir, pce); + if (unlink(tname) < 0) + break; + } + } + + +/* + * 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. + */ +int +encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum) + { + 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."); + return 1; + } + + *sum = cksum; + + return 0; + } + + +/* + * Write the mail header and data header. + */ +void +write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) + { + 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", delta, pce, npieces); + + fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, 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. + * Returns 0 on success, 1 on failure. + */ +int +apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias, + char *queue_dir) + { + FILE *sfp; + char qname[PATH_MAX]; + + if (queue_dir == NULL) + { + sfp = open_sendmail(); + if (sfp == NULL) + return 1; + } + else + { + mk_queue_name(qname, queue_dir, delta, 1, 1); + sfp = fopen(qname, "w"); + if (sfp == NULL) + { + err("cannot open '%s' for writing", qname); + return 1; + } + } + + + fprintf(sfp, "From: owner-%s\n", mail_alias); + fprintf(sfp, "To: %s\n", mail_alias); + fprintf(sfp, "Subject: ctm-notice %s\n\n", delta); + + fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta, + (long)ctm_size, max_ctm_size); + fprintf(sfp, "You can retrieve this delta via ftp.\n"); + + if (queue_dir == NULL) + { + if (!close_sendmail(sfp)) + return 1; + } + else + { + if (fclose(sfp)!=0) + { + err("error writing '%s'", qname); + unlink(qname); + return 1; + } + } + + return 0; + } + + +/* + * 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 -odq -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); + } diff --git a/usr.sbin/ctm/mkCTM/Makefile b/usr.sbin/ctm/mkCTM/Makefile new file mode 100644 index 0000000..8194dd7 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/Makefile @@ -0,0 +1,25 @@ +# $FreeBSD$ + +PROG= mkctm +MAN= + +LIBADD= md + +test: mkctm + rm -f tst.out* + time ./mkctm -v -v /3c/210src /a/r1/usr/src \ + 2>a | md5 -p > /a/tst.out + ls -l /a/tst.out + gzip -9 -v /a/tst.out + ls -l /a/tst.out.gz + # cd /usr/src/release && ctm -c -v -v ${.CURDIR}/tst.out + +test1: mkctm + rm -f tst.out* + time ./mkctm -v -v /3c/210src /home/ncvs/src \ + 2> b | md5 -p > /a/tst2.out + ls -l /a/tst2.out + gzip -9 -v /a/tst2.out + ls -l /a/tst2.out.gz + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur new file mode 100644 index 0000000..fbb5bf2 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur @@ -0,0 +1,9 @@ +#!/usr/local/bin/tclsh + +set CTMname cvs-cur +set CTMref /home/ncvs +set CTMignore {^src/secure|^src/eBones|^src/kerberosIV|^CVSROOT/val-tags$|CVSROOT/\\.#} +set CTMbogus {\\.core$|/#cvs|/\\.#} +set CTMmail ctm-cvs-cur-fast@freebsd.org +set CTMqueuemail ctm-cvs-cur@freebsd.org +set CTMqueue /home/ctm/queue/ctm-cvs-cur diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur new file mode 100644 index 0000000..b9d3d13 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur @@ -0,0 +1,7 @@ +#!/usr/local/bin/tclsh + +set CTMname ports-cur +set CTMref /usr/ports +set CTMignore {/CVS$|/CVS/|^distfiles} +set CTMbogus {\\.core$|/#cvs|/\\.#} +set CTMmail ctm-ports-cur@freebsd.org diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur new file mode 100644 index 0000000..5c3e1d1 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur @@ -0,0 +1,7 @@ +#!/usr/local/bin/tclsh + +set CTMname smp-cur +set CTMref /home/smp +set CTMignore {^CVSROOT/history.*$|^CVSROOT/val-tags$|^CVSROOT/\\.#} +set CTMbogus {\\.core$|/#cvs|/\\.#} +set CTMmail smp-cvs-cur@freebsd.org diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-cur b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur new file mode 100644 index 0000000..8589d04 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur @@ -0,0 +1,9 @@ +#!/usr/local/bin/tclsh + +set CTMname src-cur +set CTMref /c/src +set CTMignore {/CVS$|/CVS/|^/secure|^/eBones} +set CTMbogus {\\.core$|/#cvs|/\\.#} +set CTMmail ctm-src-cur-fast@freebsd.org +set CTMqueue /home/ctm/queue/ctm-src-cur +set CTMqueuemail ctm-src-cur@freebsd.org diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-special b/usr.sbin/ctm/mkCTM/ctm_conf.src-special new file mode 100644 index 0000000..2a8ca70 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-special @@ -0,0 +1,9 @@ +#!/usr/local/bin/tclsh + +set CTMname src-cur +set CTMref $CTMSW/../$CTMname +set CTMcopy /c/phk/20R/usr/src +set CTMdont {\.core$|/CVS$|/CVS/|^/secure|^/eBones|/#cvs|/\.#} +set CTMtest 1 +set CTMspecial 1 +set CTMsuff R20 diff --git a/usr.sbin/ctm/mkCTM/dequeue b/usr.sbin/ctm/mkCTM/dequeue new file mode 100755 index 0000000..697ec0b --- /dev/null +++ b/usr.sbin/ctm/mkCTM/dequeue @@ -0,0 +1,6 @@ +#! /bin/sh +# $FreeBSD$ + +L=/home/ctm/log.dequeue +/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-cvs-cur +/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-src-cur diff --git a/usr.sbin/ctm/mkCTM/mkCTM b/usr.sbin/ctm/mkCTM/mkCTM new file mode 100644 index 0000000..02b1544 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/mkCTM @@ -0,0 +1,188 @@ +#!/usr/local/bin/tclsh7.4 +# +# $FreeBSD$ + +############################################################################# +### Do we already have this delta ? +############################################################################# + +proc find_delta {nbr} { + global CTMname CTMdest + if {[file exists [format "%s/$CTMname.%04d" $CTMdest $nbr]]} { return 1 } + if {[file exists [format "%s/$CTMname.%04d.gz" $CTMdest $nbr]]} { return 1 } + return 0 +} + +############################################################################# +### The top level code... +############################################################################# + +set CTMSW /home/ctm/SW + +cd $CTMSW + +# Defaults... +set CTMapply 1 +set CTMignore {^///} +set CTMbogus {\.core$} +set CTMmail {} +set CTMqueue {} +set CTMqueuemail {} +set CTMmaxctm 10000000 +set CTMmaxmsg 100000 +set CTMsuff {} +set CTMdate [exec date -u +%Y%m%d%H%M%SZ] +set CTMtmp {} +set CTMcopy {} +set CTMdest {} +set CTMprefix . +set CTMtest 0 +set CTMspecial 0 +set CTMscan . +set CTMfirst 0 +set max_damage 100 + +set damage 0 +set changes 0 + +source $argv +exec sh -c "date -u '+%Y%m%d%H%M%S $argv'" >> ${CTMSW}/log + +if {$CTMtmp == ""} { + set CTMtmp $CTMSW/../tmp/${CTMname}_${CTMsuff} +} +if {$CTMcopy == ""} { + set CTMcopy $CTMSW/../$CTMname +} +if {$CTMdest == ""} { + set CTMdest $CTMSW/../CTM-pub/$CTMname +} + +# Make sure we only run one at a time... + +set CTMlock Lck.${CTMname}.${CTMdate}.[pid] +exec rm -f ${CTMlock} +exec echo starting > ${CTMlock} +if {[catch "exec ln $CTMlock LCK.$CTMname" a]} { + puts "Not going, lock exists..." + exec rm -f $CTMlock + exit 1 +} +exec rm -f $CTMlock +set CTMlock LCK.$CTMname + +set CTMscratch ${CTMtmp}.tmp + +while 1 { + if { ! $CTMspecial} { + if {$CTMfirst} { + set CTMnbr 0 + } else { + set CTMnbr [lindex [exec cat $CTMcopy/.ctm_status] 1] + } + + if {$CTMnbr > 0 && ![find_delta $CTMnbr]} { + puts "$CTMname delta $CTMnbr doesn't exist..." + exec rm -f $CTMlock + exit 1 + } + + incr CTMnbr + + if {[find_delta $CTMnbr]} { + puts "$CTMname delta $CTMnbr does already exist..." + exec rm -f $CTMlock + exit 1 + } + + set fo [open $CTMref/.ctm_status w] + puts $fo "$CTMname $CTMnbr" + close $fo + incr changes -1 + + } else { + set CTMnbr [lindex [exec cat $CTMref/.ctm_status] 1] + } + + puts "Doing CTMname $CTMname CTMnbr $CTMnbr$CTMsuff CTMdate $CTMdate" + flush stdout + exec sh -c "rm -f ${CTMtmp}.* ${CTMtmp}:*" >&@ stdout + + set nm [format "%s.%04d%s" $CTMname $CTMnbr $CTMsuff] + + set x1 $CTMcopy + if {$x1 == ""} { + exec mkdir ${CTMtmp}.dir + set x1 ${CTMtmp}.dir + } + set r1 [catch "exec ${CTMSW}/mkctm -I ${CTMignore} -B ${CTMbogus} -l ${CTMtmp}.log -D $max_damage $CTMname $CTMnbr $CTMdate . $x1 $CTMref | md5 -p | gzip -9 > ${CTMtmp}:${nm}.gz 2>@ stderr" r2] + + if {$r1} { + if {[lindex $errorCode 2] == 4} { + puts "No changes, stopping." + exec rm -f $CTMlock + exit 0 + } + puts "problems, stopping now." + puts "errorCode $errorCode" + puts "$r2" + exec rm -f $CTMlock + exit 1 + } + + puts "mkctm done" + + if {$CTMtest} { + puts "testing, stopping now." + exec rm -f $CTMlock + exit 0 + } + if {$CTMapply} { + puts "Applying delta" + flush stdout + exec echo now applying > $CTMlock + exec sh -e -c "cd $CTMcopy ; $CTMSW/ctm -v -v -v ${CTMtmp}:${nm}.gz" >& ${CTMtmp}.apply + exec echo did apply > $CTMlock + } + puts "Moving delta" + flush stdout + exec mv ${CTMtmp}:${nm}.gz $CTMdest/.CTMtmp_${nm}.gz >&@ stdout + exec mv $CTMdest/.CTMtmp_${nm}.gz $CTMdest/${nm}.gz >&@ stdout + exec echo moved > $CTMlock + + exec sh -c "rm -rf ${CTMtmp}.*" >&@ stdout + + if {$CTMmail != ""} { + puts "Mailing delta" + flush stdout + exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm $CTMdest/${nm}.gz $CTMmail >&@ stdout + if {$CTMqueue != "" && $CTMqueuemail != ""} { + puts "Queueing delta" + flush stdout + exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm -q $CTMqueue $CTMdest/${nm}.gz $CTMqueuemail >&@ stdout + puts "Sending initial two deltas" + flush stdout + exec $CTMSW/ctm_dequeue -n 2 $CTMqueue >&@ stdout + } + } + exec echo mailed > $CTMlock + + # If we did an absolute delta: stop. + if {$CTMsuff != ""} break + + # Make an absolute delta (!) every 100 deltas + if {$CTMnbr == 0 || ($CTMnbr % 100)} break + + # Make an absolute delta too... + set CTMref $CTMcopy + set CTMsuff A + set CTMcopy "" + set CTMmail "" + set CTMqueue "" + set CTMqueuemail "" + set CTMapply 0 + set CTMspecial 1 + exec rm -f $CTMlock +} +puts "done." +exec rm -f $CTMlock diff --git a/usr.sbin/ctm/mkCTM/mkctm.c b/usr.sbin/ctm/mkCTM/mkctm.c new file mode 100644 index 0000000..d2c73e2 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/mkctm.c @@ -0,0 +1,597 @@ +/* $FreeBSD$ */ + +/* Still missing: + * + * mkctm + * -B regex Bogus + * -I regex Ignore + * -D int Damage + * -q decrease verbosity + * -v increase verbosity + * -l file logfile + * name cvs-cur + * prefix src/secure + * dir1 "Soll" + * dir2 "Ist" + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/wait.h> +#include <dirent.h> +#include <regex.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <md5.h> +#include <err.h> +#include <paths.h> +#include <signal.h> + +#define DEFAULT_IGNORE "/CVS$|/\\.#|00_TRANS\\.TBL$" +#define DEFAULT_BOGUS "\\.core$|\\.orig$|\\.rej$|\\.o$" +regex_t reg_ignore, reg_bogus; +int flag_ignore, flag_bogus; + +int verbose; +int damage, damage_limit; +int change; + +FILE *logf; + +u_long s1_ignored, s2_ignored; +u_long s1_bogus, s2_bogus; +u_long s1_wrong, s2_wrong; +u_long s_new_dirs, s_new_files, s_new_bytes; +u_long s_del_dirs, s_del_files, s_del_bytes; +u_long s_files_chg, s_bytes_add, s_bytes_del; +u_long s_same_dirs, s_same_files, s_same_bytes; +u_long s_edit_files, s_edit_bytes, s_edit_saves; +u_long s_sub_files, s_sub_bytes; + +void +Usage(void) +{ + fprintf(stderr, + "usage: mkctm [-options] name number timestamp prefix dir1 dir2\n"); + fprintf(stderr, "options:\n"); + fprintf(stderr, "\t\t-B bogus_regexp\n"); + fprintf(stderr, "\t\t-D damage_limit\n"); + fprintf(stderr, "\t\t-I ignore_regexp\n"); + fprintf(stderr, "\t\t-q\n"); + fprintf(stderr, "\t\t-v\n"); +} + +void +print_stat(FILE *fd, char *pre) +{ + fprintf(fd, "%sNames:\n", pre); + fprintf(fd, "%s ignore: %5lu ref %5lu target\n", + pre, s1_ignored, s2_ignored); + fprintf(fd, "%s bogus: %5lu ref %5lu target\n", + pre, s1_bogus, s2_bogus); + fprintf(fd, "%s wrong: %5lu ref %5lu target\n", + pre, s1_wrong, s2_wrong); + fprintf(fd, "%sDelta:\n", pre); + fprintf(fd, "%s new: %5lu dirs %5lu files %9lu plus\n", + pre, s_new_dirs, s_new_files, s_new_bytes); + fprintf(fd, "%s del: %5lu dirs %5lu files %9lu minus\n", + pre, s_del_dirs, s_del_files, s_del_bytes); + fprintf(fd, "%s chg: %5lu files %9lu plus %9lu minus\n", + pre, s_files_chg, s_bytes_add, s_bytes_del); + fprintf(fd, "%s same: %5lu dirs %5lu files %9lu bytes\n", + pre, s_same_dirs, s_same_files, s_same_bytes); + fprintf(fd, "%sMethod:\n", pre); + fprintf(fd, "%s edit: %5lu files %9lu bytes %9lu saved\n", + pre, s_edit_files, s_edit_bytes, s_edit_saves); + fprintf(fd, "%s sub: %5lu files %9lu bytes\n", + pre, s_sub_files, s_sub_bytes); + +} + +void +stat_info(int foo) +{ + signal(SIGINFO, stat_info); + print_stat(stderr, "INFO: "); +} + +void DoDir(const char *dir1, const char *dir2, const char *name); + +static struct stat st; +static __inline struct stat * +StatFile(char *name) +{ + if (lstat(name, &st) < 0) + err(1, "couldn't stat %s", name); + return &st; +} + +int +dirselect(struct dirent *de) +{ + if (!strcmp(de->d_name, ".")) return 0; + if (!strcmp(de->d_name, "..")) return 0; + return 1; +} + +void +name_stat(const char *pfx, const char *dir, const char *name, struct dirent *de) +{ + char *buf = alloca(strlen(dir) + strlen(name) + + strlen(de->d_name) + 3); + struct stat *st; + + strcpy(buf, dir); + strcat(buf, "/"); strcat(buf, name); + strcat(buf, "/"); strcat(buf, de->d_name); + st = StatFile(buf); + printf("%s %s%s %u %u %o", + pfx, name, de->d_name, + st->st_uid, st->st_gid, st->st_mode & ~S_IFMT); + fprintf(logf, "%s %s%s\n", pfx, name, de->d_name); + if (verbose > 1) { + fprintf(stderr, "%s %s%s\n", pfx, name, de->d_name); + } +} + +void +Equ(const char *dir1, const char *dir2, const char *name, struct dirent *de) +{ + if (de->d_type == DT_DIR) { + char *p = alloca(strlen(name)+strlen(de->d_name)+2); + + strcpy(p, name); strcat(p, de->d_name); strcat(p, "/"); + DoDir(dir1, dir2, p); + s_same_dirs++; + } else { + char *buf1 = alloca(strlen(dir1) + strlen(name) + + strlen(de->d_name) + 3); + char *buf2 = alloca(strlen(dir2) + strlen(name) + + strlen(de->d_name) + 3); + char *m1, md5_1[33], *m2, md5_2[33]; + u_char *p1, *p2; + int fd1, fd2; + struct stat s1, s2; + + strcpy(buf1, dir1); + strcat(buf1, "/"); strcat(buf1, name); + strcat(buf1, "/"); strcat(buf1, de->d_name); + fd1 = open(buf1, O_RDONLY); + if(fd1 < 0) { err(3, "%s", buf1); } + fstat(fd1, &s1); + strcpy(buf2, dir2); + strcat(buf2, "/"); strcat(buf2, name); + strcat(buf2, "/"); strcat(buf2, de->d_name); + fd2 = open(buf2, O_RDONLY); + if(fd2 < 0) { err(3, "%s", buf2); } + fstat(fd2, &s2); +#if 0 + /* XXX if we could just trust the size to change... */ + if (s1.st_size == s2.st_size) { + s_same_files++; + s_same_bytes += s1.st_size; + close(fd1); + close(fd2); + goto finish; + } +#endif + p1=mmap(0, s1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0); + if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf1); } + close(fd1); + + p2=mmap(0, s2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0); + if (p2 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); } + close(fd2); + + /* If identical, we're done. */ + if((s1.st_size == s2.st_size) && !memcmp(p1, p2, s1.st_size)) { + s_same_files++; + s_same_bytes += s1.st_size; + goto finish; + } + + s_files_chg++; + change++; + if (s1.st_size > s2.st_size) + s_bytes_del += (s1.st_size - s2.st_size); + else + s_bytes_add += (s2.st_size - s1.st_size); + + m1 = MD5Data(p1, s1.st_size, md5_1); + m2 = MD5Data(p2, s2.st_size, md5_2); + + /* Just a curiosity... */ + if(!strcmp(m1, m2)) { + if (s1.st_size != s2.st_size) + fprintf(stderr, + "Notice: MD5 same for files of diffent size:\n\t%s\n\t%s\n", + buf1, buf2); + goto finish; + } + + { + u_long l = s2.st_size + 2; + u_char *cmd = alloca(strlen(buf1)+strlen(buf2)+100); + u_char *ob = malloc(l), *p; + int j; + FILE *F; + + if (s1.st_size && p1[s1.st_size-1] != '\n') { + if (verbose > 0) + fprintf(stderr, + "last char != \\n in %s\n", + buf1); + goto subst; + } + + if (s2.st_size && p2[s2.st_size-1] != '\n') { + if (verbose > 0) + fprintf(stderr, + "last char != \\n in %s\n", + buf2); + goto subst; + } + + for (p=p1; p<p1+s1.st_size; p++) + if (!*p) { + if (verbose > 0) + fprintf(stderr, + "NULL char in %s\n", + buf1); + goto subst; + } + + for (p=p2; p<p2+s2.st_size; p++) + if (!*p) { + if (verbose > 0) + fprintf(stderr, + "NULL char in %s\n", + buf2); + goto subst; + } + + strcpy(cmd, "diff -n "); + strcat(cmd, buf1); + strcat(cmd, " "); + strcat(cmd, buf2); + F = popen(cmd, "r"); + for (j = 1, l = 0; l < s2.st_size; ) { + j = fread(ob+l, 1, s2.st_size - l, F); + if (j < 1) + break; + l += j; + continue; + } + if (j) { + l = 0; + while (EOF != fgetc(F)) + continue; + } + pclose(F); + + if (l && l < s2.st_size) { + name_stat("CTMFN", dir2, name, de); + printf(" %s %s %d\n", m1, m2, (unsigned)l); + fwrite(ob, 1, l, stdout); + putchar('\n'); + s_edit_files++; + s_edit_bytes += l; + s_edit_saves += (s2.st_size - l); + } else { + subst: + name_stat("CTMFS", dir2, name, de); + printf(" %s %s %u\n", m1, m2, (unsigned)s2.st_size); + fwrite(p2, 1, s2.st_size, stdout); + putchar('\n'); + s_sub_files++; + s_sub_bytes += s2.st_size; + } + free(ob); + } + finish: + munmap(p1, s1.st_size); + munmap(p2, s2.st_size); + } +} + +void +Add(const char *dir1, const char *dir2, const char *name, struct dirent *de) +{ + change++; + if (de->d_type == DT_DIR) { + char *p = alloca(strlen(name)+strlen(de->d_name)+2); + strcpy(p, name); strcat(p, de->d_name); strcat(p, "/"); + name_stat("CTMDM", dir2, name, de); + putchar('\n'); + s_new_dirs++; + DoDir(dir1, dir2, p); + } else if (de->d_type == DT_REG) { + char *buf2 = alloca(strlen(dir2) + strlen(name) + + strlen(de->d_name) + 3); + char *m2, md5_2[33]; + u_char *p1; + struct stat st; + int fd1; + + strcpy(buf2, dir2); + strcat(buf2, "/"); strcat(buf2, name); + strcat(buf2, "/"); strcat(buf2, de->d_name); + fd1 = open(buf2, O_RDONLY); + if (fd1 < 0) { err(3, "%s", buf2); } + fstat(fd1, &st); + p1=mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd1, 0); + if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); } + close(fd1); + m2 = MD5Data(p1, st.st_size, md5_2); + name_stat("CTMFM", dir2, name, de); + printf(" %s %u\n", m2, (unsigned)st.st_size); + fwrite(p1, 1, st.st_size, stdout); + putchar('\n'); + munmap(p1, st.st_size); + s_new_files++; + s_new_bytes += st.st_size; + } +} + +void +Del (const char *dir1, const char *dir2, const char *name, struct dirent *de) +{ + damage++; + change++; + if (de->d_type == DT_DIR) { + char *p = alloca(strlen(name)+strlen(de->d_name)+2); + strcpy(p, name); strcat(p, de->d_name); strcat(p, "/"); + DoDir(dir1, dir2, p); + printf("CTMDR %s%s\n", name, de->d_name); + fprintf(logf, "CTMDR %s%s\n", name, de->d_name); + if (verbose > 1) { + fprintf(stderr, "CTMDR %s%s\n", name, de->d_name); + } + s_del_dirs++; + } else if (de->d_type == DT_REG) { + char *buf1 = alloca(strlen(dir1) + strlen(name) + + strlen(de->d_name) + 3); + char *m1, md5_1[33]; + strcpy(buf1, dir1); + strcat(buf1, "/"); strcat(buf1, name); + strcat(buf1, "/"); strcat(buf1, de->d_name); + m1 = MD5File(buf1, md5_1); + printf("CTMFR %s%s %s\n", name, de->d_name, m1); + fprintf(logf, "CTMFR %s%s %s\n", name, de->d_name, m1); + if (verbose > 1) { + fprintf(stderr, "CTMFR %s%s\n", name, de->d_name); + } + s_del_files++; + s_del_bytes += StatFile(buf1)->st_size; + } +} + +void +GetNext(int *i, int *n, struct dirent **nl, const char *dir, const char *name, u_long *ignored, u_long *bogus, u_long *wrong) +{ + char buf[BUFSIZ]; + char buf1[BUFSIZ]; + + for (;;) { + for (;;) { + (*i)++; + if (*i >= *n) + return; + strcpy(buf1, name); + if (buf1[strlen(buf1)-1] != '/') + strcat(buf1, "/"); + strcat(buf1, nl[*i]->d_name); + if (flag_ignore && + !regexec(®_ignore, buf1, 0, 0, 0)) { + (*ignored)++; + fprintf(logf, "Ignore %s\n", buf1); + if (verbose > 2) { + fprintf(stderr, "Ignore %s\n", buf1); + } + } else if (flag_bogus && + !regexec(®_bogus, buf1, 0, 0, 0)) { + (*bogus)++; + fprintf(logf, "Bogus %s\n", buf1); + fprintf(stderr, "Bogus %s\n", buf1); + damage++; + } else { + *buf = 0; + if (*dir != '/') + strcat(buf, "/"); + strcat(buf, dir); + if (buf[strlen(buf)-1] != '/') + strcat(buf, "/"); + strcat(buf, buf1); + break; + } + free(nl[*i]); nl[*i] = 0; + } + /* If the filesystem didn't tell us, find type */ + if (nl[*i]->d_type == DT_UNKNOWN) + nl[*i]->d_type = IFTODT(StatFile(buf)->st_mode); + if (nl[*i]->d_type == DT_REG || nl[*i]->d_type == DT_DIR) + break; + (*wrong)++; + if (verbose > 0) + fprintf(stderr, "Wrong %s\n", buf); + free(nl[*i]); nl[*i] = 0; + } +} + +void +DoDir(const char *dir1, const char *dir2, const char *name) +{ + int i1, i2, n1, n2, i; + struct dirent **nl1, **nl2; + char *buf1 = alloca(strlen(dir1) + strlen(name) + 4); + char *buf2 = alloca(strlen(dir2) + strlen(name) + 4); + + strcpy(buf1, dir1); strcat(buf1, "/"); strcat(buf1, name); + strcpy(buf2, dir2); strcat(buf2, "/"); strcat(buf2, name); + n1 = scandir(buf1, &nl1, dirselect, alphasort); + n2 = scandir(buf2, &nl2, dirselect, alphasort); + i1 = i2 = -1; + GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong); + GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong); + for (;i1 < n1 || i2 < n2;) { + + if (damage_limit && damage > damage_limit) + break; + + /* Get next item from list 1 */ + if (i1 < n1 && !nl1[i1]) + GetNext(&i1, &n1, nl1, dir1, name, + &s1_ignored, &s1_bogus, &s1_wrong); + + /* Get next item from list 2 */ + if (i2 < n2 && !nl2[i2]) + GetNext(&i2, &n2, nl2, dir2, name, + &s2_ignored, &s2_bogus, &s2_wrong); + + if (i1 >= n1 && i2 >= n2) { + /* Done */ + break; + } else if (i1 >= n1 && i2 < n2) { + /* end of list 1, add anything left on list 2 */ + Add(dir1, dir2, name, nl2[i2]); + free(nl2[i2]); nl2[i2] = 0; + } else if (i1 < n1 && i2 >= n2) { + /* end of list 2, delete anything left on list 1 */ + Del(dir1, dir2, name, nl1[i1]); + free(nl1[i1]); nl1[i1] = 0; + } else if (!(i = strcmp(nl1[i1]->d_name, nl2[i2]->d_name))) { + /* Identical names */ + if (nl1[i1]->d_type == nl2[i2]->d_type) { + /* same type */ + Equ(dir1, dir2, name, nl1[i1]); + } else { + /* different types */ + Del(dir1, dir2, name, nl1[i1]); + Add(dir1, dir2, name, nl2[i2]); + } + free(nl1[i1]); nl1[i1] = 0; + free(nl2[i2]); nl2[i2] = 0; + } else if (i < 0) { + /* Something extra in list 1, delete it */ + Del(dir1, dir2, name, nl1[i1]); + free(nl1[i1]); nl1[i1] = 0; + } else { + /* Something extra in list 2, add it */ + Add(dir1, dir2, name, nl2[i2]); + free(nl2[i2]); nl2[i2] = 0; + } + } + if (n1 >= 0) + free(nl1); + if (n2 >= 0) + free(nl2); +} + +int +main(int argc, char **argv) +{ + int i; + + setbuf(stderr, NULL); + +#if 0 + if (regcomp(®_bogus, DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + errx(1, "default regular expression argument to -B is botched"); + flag_bogus = 1; + + if (regcomp(®_ignore, DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + errx(1, "default regular expression argument to -I is botched"); + flag_ignore = 1; +#endif + + while ((i = getopt(argc, argv, "D:I:B:l:qv")) != -1) + switch (i) { + case 'D': + damage_limit = strtol(optarg, 0, 0); + if (damage_limit < 0) + errx(1, "damage limit must be positive"); + break; + case 'I': + if (flag_ignore) + regfree(®_ignore); + flag_ignore = 0; + if (!*optarg) + break; + if (regcomp(®_ignore, optarg, + REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + errx(1, "regular expression argument to -I is botched"); + flag_ignore = 1; + break; + case 'B': + if (flag_bogus) + regfree(®_bogus); + flag_bogus = 0; + if (!*optarg) + break; + if (regcomp(®_bogus, optarg, + REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + errx(1, "regular expression argument to -B is botched"); + flag_bogus = 1; + break; + case 'l': + logf = fopen(optarg, "w"); + if (!logf) + err(1, "%s", optarg); + setlinebuf(logf); + break; + case 'q': + verbose--; + break; + case 'v': + verbose++; + break; + case '?': + default: + Usage(); + return (1); + } + argc -= optind; + argv += optind; + + if (!logf) + logf = fopen(_PATH_DEVNULL, "w"); + + setbuf(stdout, 0); + + if (argc != 6) { + Usage(); + return (1); + } + + signal(SIGINFO, stat_info); + + fprintf(stderr, "CTM_BEGIN 2.0 %s %s %s %s\n", + argv[0], argv[1], argv[2], argv[3]); + fprintf(logf, "CTM_BEGIN 2.0 %s %s %s %s\n", + argv[0], argv[1], argv[2], argv[3]); + printf("CTM_BEGIN 2.0 %s %s %s %s\n", + argv[0], argv[1], argv[2], argv[3]); + DoDir(argv[4], argv[5], ""); + if (damage_limit && damage > damage_limit) { + print_stat(stderr, "DAMAGE: "); + errx(1, "damage of %d would exceed %d files", + damage, damage_limit); + } else if (change < 2) { + errx(4, "no changes"); + } else { + printf("CTM_END "); + fprintf(logf, "CTM_END\n"); + print_stat(stderr, "END: "); + } + exit(0); +} |