[net.sources] mix fix

istvan@hhb.UUCP (04/14/87)

In order to enable mailing of "mix"-ed messages like

--------------------------- cut here ---------------------------------
 atcmne yscedfcliodpfhl wiaa cih a whadnnt tsIairme.onii fciooaops shi
ineaeaeC g nhee scnmc iate u- taeen
cfmisrfrwgrrT   bs   noitihe h ygfiks xdreipnshnt
i ia.' n tacli u s ot ls tsh slea anhb osoeaaln
nntrncmepudcosoeutqu aoi ld sufotuump ,c cdwtlt ay irsriswsp otd
stowtner srut v ctioo r
t soh yaoahtoohfurerthaoetg iu
--------------------------- cut here ---------------------------------

Rev A of mix.c (re-posted to net.sources on Apr 14, 1987) is modified
to leave the last byte (newline) of a file undisturbed.  Before changing
over to the new version, files encoded with the old version should be
decoded with the old version; or alternately a newline should be
appended to the old cyphertext.

     BRIEF:
     In-place (non-filter) encoding/decoding of ASCII files, using
     an interactively acquired key, or the value of the environment
     variable MIXKEY, or one constructed from user's effective user id.
     Shuffles the bytes of the file; the retained character set
     permits ASCII file transfers.  Provides the key holder immediate
     access (vi, grep, make, etc.) to scrambled files such that the
     scrambled nature of the files is transparent.  The objects
     edmix, keymix, unmix, catmix are links of mix, argv[0] is
     hashed to decode context.

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# mix.man mix.c

echo x - mix.man
cat > "mix.man" << '//E*O*F mix.man//'
.TH MIX hhb "" CMC
.UC 4
.SH NAME
.nh
.nf
\fBmix\ unmix\ catmix\ keymix\ edmix\fR \ \ - source code protection routines

\fBmix\fR  		- scramble a file
\fBunmix\fR		- descramble a file
\fBcatmix\fR		- print a scrambled file
\fBkeymix\fR		- print the minimum security key
\fBedmix\fR		- edit a scrambled file

.SH SYNOPSYS
.nf
.B mix\ \ \t\ [\ -k\ key\ ]\ \ files
.B unmix\t\ [\ -k\ key\ ]\ \ files
.B catmix\t\ [\ -k\ key\ ]\ \ files
.B keymix
.B edmix\t\ [\ -k\ key\ ]\ [\ -e\ exec_cmd\ ]\ \ files

.fi
.SH DESCRIPTION
The \fBmix\fR routines scramble/descramble
ASCII files based on
a supplied keyword.  Users in possession of the
key have immediate access to scrambled files
such that the scrambled nature of the files is transparent.
The routines link to a single object.
.PP
\fBMix\fR scrambles, \fBunmix\fR
descrambles the files in its argument list.  For each file in its argument list,
\fBedmix\fR descrambles the file, effects a
system call of the form \fBsystem(exec_cmd file)\fR, followed by
rescrambling the file.  \fBCatmix\fR writes a descrambled version of
its arguments to stdout.  \fBKeymix\fR prints the
"minimum security" key to stderr.
A given key affects the entire argument list.
.PP
The user normally supplies the key for the transformation mechanism.
Allowed key lengths are 1 through 128 bytes.  Longer keys are
silently truncated to 128 bytes; null keys
trigger the "minimum security" option instead.
.PP
Performance characteristics are comparable to those of the standard
\fBcrypt\fR command, while not incurring the restrictions that limit
the use of \fBcrypt\fR to the United States.
Unlike \fBcrypt\fR, the executable
is not a filter, precluding the necessity for redirecting output to
temporary files followed by a move of the temporary files to the
original files.  Also unlike \fBcrypt\fR, the scrambling effect is not the same
as the descrambling effect: two successive scramblings do not return a file
to its original form.  The command "mix myfile myfile" for example,
twice scrambles myfile; the appropriate reversal is
"unmix myfile myfile".  Still another difference with respect to \fBcrypt\fR
is that the character
set of a file scrambled by \fBmix\fR
is identical to that of the original file,
permitting remote file transfer
protocol without "control characters".
.PP
The \fBmix\fR
routines do not alter files that contain less than 256 bytes,
or files containing null (ASCII zero) bytes.  When encountering
such, a warning message is generated, without any other side effects.
A \fBmix\fR process locks the file on which it operates; concurrent
\fBmix\fR
processes competing for the same file wait until the locking
process completes the transformation and releases the lock.
.sp
.PP
.SH KEY ACQUISITION
The \fB-k key\fR command line option directly specifies the key for
the transformations, overriding the two
other methods described below.
Due to the visible records of the command line,
this option is not the
most secure alternative.
.PP
The environment variable \fBMIXKEY\fR value if non-null, is
automatically used as the key throughout the life of the user's shell.
The command "setenv MIXKEY key" assigns a value to \fBMIXKEY\fR under
the Berkeley system.  The System V equivalent is "MIXKEY=key ;
export MIXKEY".
.PP
Key acquisition is interactive in lieu of the above methods.
Echoing is turned
off and the user responds to the "Key: " prompt appearing
on stderr.
The commands reprompt
with "Again: "
under "Key: " and verify the match to avoid typos.
On a mismatch, a control-G (bel) is printed, and
the program exits without any file access.
.PP
For minimum security applications, the \fBnull key\fR
given by the user (interactively or with the command line
option \fB-k ""\fR) is supplanted with one
constructed from the effective user id of the user.  This permits
a number of individuals logging in to an environment,
easy access to information scrambled in that environment, while
excluding others.  For example, if myfile is scrambled by \fBproject\fR
using
"mix myfile < nullfile" where a redirected, empty "nullfile" supplies
a zero length key to the interactive acquisition routine, all users
logging in as \fBproject\fR
have continued access to the original "myfile" using
a null key.
Note that scrambling/descrambling with minimum security keys does not port
to other environments, unless the user provides the key previously
acquired
in the \fBoriginal\fR environment through the \fBkeymix\fR command.
.sp
.PP
.SH EDMIX COMMANDS
The default \fBsystem\fR command executed by \fBedmix\fR is the
\fBvi\fR editor.  Thus, executing
"edmix myfile.c myfile.h" commences with key acquisition, then myfile.c is
descrambled, a system("vi myfile.c") call follows, then - the possibly
modified - myfile.c is rescrambled.  Myfile.h is processed next, using the
same key.
.PP
The \fB-e exec_cmd\fR supplants the default \fBvi\fR
system call with exec_cmd.  Thus, "edmix -e emacs myfile"
allows direct editing of the unscrambled version
of a scrambled "myfile",
using the \fBemacs\fR editor.  Exec_cmd normally is
a command that takes a file name as a last argument.
Alternately, dollar ($) signs contained by exec_cmd
are expanded to the name of the currently descrambled file.
Commands longer than a word should be quoted.  Examples:
.nf

edmix -e "grep xyz" file.c file.h
edmix -e '( echo $; cat -n $ | grep xyz )  >>  lines_of_xyz' file.c file.h
.fi
.sp
.SH DATA INTEGRITY
All file reads and writes performed by the \fBmix\fR routines
are single (atomic)
operations pertaining to the length of the file, to minimize
the danger of mangled data.  User interrupts are
guaranteed to leave the entirety of a file either in its scrambled or in
its descrambled state.  However the operating system
using delayed writes to disk, may perform an incomplete flush
during a system crash and make a file unrecoverable.  For
this reason keeping some form of backup source is recommended.
.PP
Shell wild cards occasionally have the effect of cueing multiple
instances of the same file in the argument list.  The command
"mix myfile*  *.c"  for example, scrambles myfile.c twice in succession;
a later "edmix myfile.c" fills the \fBvi\fR buffer with
a first generation scrambling of myfile.c.
.PP
Loss of a key, not surprisingly, will present the user with a
substantial exercise in cryptography.
//E*O*F mix.man//

echo x - mix.c
cat > "mix.c" << '//E*O*F mix.c//'
/* mix.c */
/**********************************************************************
*    File Name     : mix.c
*    Object        : mix - also link to unmix, edmix, catmix, keymix
*    Berkeley cc   : cc -s -O % -o $BIN/mix
*    System V cc   : cc -s -O -DREALUNIX % -o $BIN/mix
*    Author        : Istvan Mohos, March 1987
*    Rev A Apr 14  : Do not mix last byte of file
***********************************************************************/


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>

#ifdef REALUNIX
#include <termio.h>

struct termio tbuf, tbufsave;
echo_off()
{
    ioctl (0, TCGETA, &tbuf);
    tbufsave = tbuf;
    tbuf.c_lflag &= ~ECHO;
    ioctl (0, TCSETAF, &tbuf);
}

echo_ret()
{
    ioctl (0, TCSETAF, &tbufsave);
}

#else
#include <sys/ioctl.h>

struct sgttyb tbuf, tbufsave;
echo_off()
{
    ioctl (0, TIOCGETP, &tbuf);
    tbufsave = tbuf;
    tbuf.sg_flags &= ~ECHO;
    ioctl (0, TIOCSETP, &tbuf);
}

echo_ret()
{
    ioctl (0, TIOCSETP, &tbufsave);
}

#endif

#define SMALLIM 256    /* minimum file size */
#define KMAX 128       /* maximum key size */

#define NONE    -1     /* data buffer references */
#define PLAIN    0
#define CIPHER   1
#define SMALLBUF 2
#define SCREEN   1

#define MIX    334     /* argv[0] hash values */
#define EDMIX  535
#define UNMIX  561
#define CATMIX 646
#define KEYMIX 663

struct keychar {       /* links to form chain of key bytes */
    int val;
    struct keychar *next;
};

int fstat();
struct stat sbuf;

int fd = -1, lockfd;          /* subject file, lock file */
char *plain, *cipher;         /* malloc pointers */
char *head, *tail;            /* main buffer limits */
char *gets(), *getenv();
struct keychar key[KMAX];     /* links */
int klen, flen;               /* key length, file length */
int loop;                     /* negative offset: size of main buffer */

int eflag = 0;                /* edmix command flag */
int unphase;                  /* unmix phase of edmix command */
char *estring = "vi";         /* edmix default command */
static char cmd[SMALLIM];     /* edmix system command buffer */

int argindx = 1;              /* pointer to current file */
char *fstring;                /* current file name */
static char mstring[KMAX];    /* minimum security key buffer */
char *kstring;                /* pointer to user key string */
char smallbuf[SMALLIM];       /* file buffer for files less than SMALLIM */
char lockstr[SMALLIM];        /* lock file: /tmp/mixlock$inode */
int locked = 0;               /* lock flag */
int context = 0;              /* argv[0] hash value */
int cleanup();                /* remove lock file, exit */

extern char **environ;
extern int errno;

main(argc,argv)
int argc;
char *argv[];
{

    register char *here;
    int regi;
    int donttry;              /* if unphase of edmix fails */
    int argneed = 2;
    char kbuf[SMALLIM];       /* interactive key acquisition buffers */
    char verify[SMALLIM];

    if (signal(SIGHUP, SIG_IGN) == SIG_DFL)
        signal(SIGHUP, cleanup);
    if (signal(SIGINT, SIG_IGN) == SIG_DFL)
        signal(SIGINT, cleanup);
    if (signal(SIGQUIT, SIG_IGN) == SIG_DFL)
        signal(SIGQUIT, cleanup);
    if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
        signal(SIGTERM, cleanup);

    here = argv[0] + strlen(argv[0]);
    while(--here > argv[0]) {
        if (*here == '/') {
           ++here;
           break;
        }
    }
    if (*here == '/')  /* first byte of argv[0] */
       ++here;

    while(*here)
        context += *here++;

    switch (context) {
        case KEYMIX:
        fillm();
        fprintf(stderr, "%s\n", mstring), cleanup(0);

        case EDMIX:
            if (argc > argindx) {
                for (regi = 1; regi < 5;) {
                    if (strcmp(argv[regi], "-k") == 0) {
                        argneed += 2;
                        argindx += 2;
                        if (++regi < argc)
                            kstring = argv[regi];
                    }
                    else if (strcmp(argv[regi], "-e") == 0) {
                        eflag = 1;
                        argneed += 2;
                        argindx += 2;
                        if (++regi < argc)
                            estring = argv[regi];
                    }
                    if (++regi >= argc)
                        break;
                }
            }
            break;

        default:
            if (argc > argindx) {
                for (regi = 1; regi < 3;) {
                    if (strcmp(argv[regi], "-k") == 0) {
                        argneed += 2;
                        argindx += 2;
                        if (++regi < argc)
                            kstring = argv[regi];
                    }
                    if (++regi >= argc)
                        break;
                }
            }
            break;
    }

    if (argneed > argc) {
        switch (context) {
            default:
                fprintf(stderr, "Usage: %s [ -k key ] file ...\n",
                    argv[0]), cleanup(0);

            case EDMIX:
                fprintf(stderr,
                    "Usage: %s [ -k key ] [ -e editor ] file ...\n",
                    argv[0]), cleanup(0);
        }
    }

    if (kstring == NULL) {
        kstring = getenv("MIXKEY");
        if (kstring != NULL)
            if (strlen(kstring) == 0)
                kstring = NULL;
            /* to prevent a NULL environment variable automatically
               triggering the euid conversion */
    }

    if (kstring == NULL) {
        kstring = kbuf;
        fprintf(stderr, "Key: "), fflush(stderr);
        echo_off();
        gets(kstring);
        fprintf(stderr, "\n");
        fprintf(stderr, "Again: "), fflush(stderr);
        gets(verify);
        fprintf(stderr, "\n");
        echo_ret();
        if (strcmp(kstring, verify) != 0)
            fprintf(stderr, "%c%c",7, 7), cleanup(1);
    }

    /* verify key length validity */
    if ((klen = strlen(kstring)) == 0) {
        kstring = mstring;
        fillm();
        klen = strlen(kstring);
    }
    else if (klen > KMAX)
        *(kstring + KMAX) = '\0', klen = KMAX;

    /* link up selectors into circular chain */
    for (regi = klen; --regi; key[regi].next = key + regi -1);
    key[0].next = key + klen -1;

    while (argindx < argc) {
        fstring = argv[argindx++];
        donttry = 0;

        switch (context) {
            case MIX:
                switch (readfile(PLAIN)) {
                    case 0:
                        mix();
                        writefile(CIPHER, 0);
                        break;

                    case 1:
                        break;

                    default:
                        writefile(NONE, 0);
                        break;
                }
                break;

            case UNMIX:
                switch (readfile(CIPHER)) {
                    case 0:
                        unmix();
                        writefile(PLAIN, 0);
                        break;

                    case 1:
                        break;

                    default:
                        writefile(NONE, 0);
                        break;
                }
                break;

            case EDMIX:
                unphase = 1;
                switch(readfile(CIPHER)) {
                    case 0:
                        unmix();
                        writefile(PLAIN, 0);
                        break;

                    case 1:
                        donttry = 1;
                        break;

                    default:
                        writefile(NONE, 0);
                        break;
                }

                unphase = 0;
                if (!donttry) {
                    mksys();
                    system(cmd);
    
                    switch(readfile(PLAIN)) {
                        case 0:
                            mix();
                            writefile(CIPHER, 0);
                            break;

                        default:
                            writefile(NONE, 0);
                            break;
                    }
                }
                break;

            case CATMIX:
            default:
                switch(readfile(CIPHER)) {
                    case 0:
                        unmix();
                        writefile(PLAIN, SCREEN);
                        break;

                    case 1:
                        break;

                    default:
                        writefile(SMALLBUF, SCREEN);
                        break;
                }
                break;
        }
    }
    cleanup(0);
}

/* hash buffer to manufacture minimum security key,
   filled byte-by-byte to silence the "strings" command */
static260(buf)
char *buf;
{
    register char *h = buf;

    *h++ = 106; *h++ = 97;  *h++ = 101; *h++ = 111; *h++ = 108;
    *h++ = 102; *h++ = 107; *h++ = 108; *h++ = 116; *h++ = 106;
    *h++ = 109; *h++ = 118; *h++ = 114; *h++ = 107; *h++ = 111;
    *h++ = 111; *h++ = 118; *h++ = 107; *h++ = 109; *h++ = 117;
    *h++ = 112; *h++ = 104; *h++ = 109; *h++ = 108; *h++ = 114;
    *h++ = 103; *h++ = 105; *h++ = 112; *h++ = 106; *h++ = 97;
    *h++ = 99;  *h++ = 98;  *h++ = 103; *h++ = 117; *h++ = 117;
    *h++ = 97;  *h++ = 116; *h++ = 106; *h++ = 107; *h++ = 104;
    *h++ = 110; *h++ = 122; *h++ = 121; *h++ = 100; *h++ = 119;
    *h++ = 111; *h++ = 112; *h++ = 109; *h++ = 112; *h++ = 99;
    *h++ = 97;  *h++ = 101; *h++ = 118; *h++ = 107; *h++ = 106;
    *h++ = 101; *h++ = 104; *h++ = 114; *h++ = 113; *h++ = 116;
    *h++ = 107; *h++ = 120; *h++ = 119; *h++ = 113; *h++ = 114;
    *h++ = 97;  *h++ = 119; *h++ = 121; *h++ = 110; *h++ = 122;
    *h++ = 120; *h++ = 114; *h++ = 115; *h++ = 98;  *h++ = 120;
    *h++ = 97;  *h++ = 112; *h++ = 98;  *h++ = 121; *h++ = 115;
    *h++ = 116; *h++ = 99;  *h++ = 121; *h++ = 98;  *h++ = 113;
    *h++ = 100; *h++ = 122; *h++ = 116; *h++ = 117; *h++ = 100;
    *h++ = 122; *h++ = 99;  *h++ = 114; *h++ = 101; *h++ = 100;
    *h++ = 117; *h++ = 118; *h++ = 101; *h++ = 97;  *h++ = 100;
    *h++ = 115; *h++ = 105; *h++ = 101; *h++ = 118; *h++ = 119;
    *h++ = 102; *h++ = 98;  *h++ = 101; *h++ = 116; *h++ = 106;
    *h++ = 102; *h++ = 119; *h++ = 120; *h++ = 103; *h++ = 99;
    *h++ = 102; *h++ = 118; *h++ = 107; *h++ = 103; *h++ = 120;
    *h++ = 121; *h++ = 105; *h++ = 100; *h++ = 103; *h++ = 119;
    *h++ = 108; *h++ = 106; *h++ = 121; *h++ = 122; *h++ = 106;
    *h++ = 101; *h++ = 104; *h++ = 120; *h++ = 110; *h++ = 108;
    *h++ = 122; *h++ = 97;  *h++ = 107; *h++ = 102; *h++ = 105;
    *h++ = 122; *h++ = 113; *h++ = 109; *h++ = 98;  *h++ = 98;
    *h++ = 108; *h++ = 103; *h++ = 107; *h++ = 97;  *h++ = 99;
    *h++ = 112; *h++ = 99;  *h++ = 101; *h++ = 109; *h++ = 104;
    *h++ = 108; *h++ = 98;  *h++ = 102; *h++ = 115; *h++ = 100;
    *h++ = 103; *h++ = 110; *h++ = 105; *h++ = 109; *h++ = 99;
    *h++ = 104; *h++ = 116; *h++ = 102; *h++ = 105; *h++ = 111;
    *h++ = 108; *h++ = 110; *h++ = 100; *h++ = 106; *h++ = 117;
    *h++ = 103; *h++ = 108; *h++ = 112; *h++ = 109; *h++ = 111;
    *h++ = 102; *h++ = 109; *h++ = 120; *h++ = 104; *h++ = 110;
    *h++ = 113; *h++ = 110; *h++ = 112; *h++ = 104; *h++ = 111;
    *h++ = 121; *h++ = 105; *h++ = 112; *h++ = 115; *h++ = 111;
    *h++ = 113; *h++ = 105; *h++ = 113; *h++ = 122; *h++ = 106;
    *h++ = 114; *h++ = 116; *h++ = 113; *h++ = 114; *h++ = 107;
    *h++ = 115; *h++ = 119; *h++ = 114; *h++ = 97;  *h++ = 120;
    *h++ = 115; *h++ = 115; *h++ = 108; *h++ = 118; *h++ = 121;
    *h++ = 116; *h++ = 98;  *h++ = 122; *h++ = 117; *h++ = 117;
    *h++ = 109; *h++ = 119; *h++ = 98;  *h++ = 118; *h++ = 99;
    *h++ = 99;  *h++ = 119; *h++ = 110; *h++ = 110; *h++ = 120;
    *h++ = 100; *h++ = 121; *h++ = 102; *h++ = 101; *h++ = 121;
    *h++ = 102; *h++ = 122; *h++ = 113; *h++ = 115; *h++ = 100;
    *h++ = 111; *h++ = 116; *h++ = 103; *h++ = 103; *h++ = 104;
    *h++ = 104; *h++ = 105; *h++ = 105; *h++ = 117; *h++ = 110;
    *h++ = 111; *h++ = 113; *h++ = 112; *h++ = 114; *h++ = 118;
    *h++ = 115; *h++ = 117; *h++ = 118; *h++ = 119; *h++ = 120;
    *h = 0; }

/* manufacture minimum security key */
fillm()
{
    register char *here;
    int regi;
    int ck, mlen;
    static char fbuf[12], mbuf[36];
    char bstring[261];
    char *from, *to;

    ck = geteuid();
    ++ck; /* don't want root dividing by 0 */
    strcpy(fbuf, "%c");
    strcat(fbuf, "%o");
    strcat(fbuf, " %");
    strcat(fbuf, "x ");
    strcat(fbuf, "%d"); /* just so "strings" couldn't find it */
    sprintf(mbuf, fbuf, (ck & 95) + ' ' , ck, ~ck & 0xfff,
        (~ck & 0xffff) / ck);
    static260(bstring);
    head = bstring;
    here = head + strlen(bstring);
    tail = here -1;
    loop = head - tail -1;
    from = mbuf;
    to = mstring;
    mlen = strlen(mbuf);
    for (regi = mlen; --regi >=0; ) {
        if ((here += *from++) > tail)
            here += loop;
        *to++ = *here;
    }
}

/* manufacture system command of edmix */
mksys()
{
    char *here;
    char *esp;
    int fnamlen, did_sub = 0;

    if (!eflag) {
        strcpy(cmd, estring);
        strcat(cmd, " ");
        strcat(cmd, fstring);
    }
    else {
        here = cmd;
        esp = estring;
        fnamlen = strlen(fstring);
        while (*esp) {
            if (*esp == '$') {
                strcpy(here, fstring);
                here += fnamlen;
                esp++;
                did_sub = 1;
            }
            else
               *here++ = *esp++;
        }
        if (!did_sub) {
            *here++ = ' ';
            strcpy(here, fstring);
            here += fnamlen;
        }
        *here = 0;
    }
}

cleanup(exitval)
int exitval;
{
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    if (locked)
        unlock();
    exit(exitval);
}

lock()
{
    sprintf(lockstr, "/tmp/mixlock%d", sbuf.st_ino);
    while ((lockfd = creat(lockstr, 0)) == -1 && errno == EACCES) {
        fprintf(stderr,
            "Concurrent process lock %s, retry in 1 sec.\n", lockstr);
        sleep(1);
    }
    locked = 1;
    close(lockfd);
}

unlock()
{
    if (unlink(lockstr) == -1)
        fprintf(stderr,
            "Cannot unlink process lock %s, exiting\n", lockstr), exit(1);
    locked = 0;
}

readfile(source)
int source;
{
    int checkval;
    char *where;
    register char *here;
    char *calloc(), *malloc();

    if ((fd = open(fstring, 2)) == -1) {
        fprintf(stderr, "File not changed: %s (no access)\n", fstring);
        return(1);
    }
        
    if ((checkval = fstat(fd, &sbuf)) == -1)
        fprintf(stderr, "File system read error %d at %s, exiting\n",
        checkval, fstring), cleanup(1);

    if (context != EDMIX || unphase)
        lock();

    if ((flen = (int)sbuf.st_size) < SMALLIM) {
        fprintf(stderr, "File not changed: %s (less than %d bytes)\n",
        fstring, SMALLIM);
        return(2);
    }

    if (((plain = calloc((unsigned)flen, 1)) == NULL) ||
        ((cipher = malloc((unsigned)flen)) == NULL))
        fprintf(stderr, "Can't allocate %lu bytes at %s, exiting\n",
            flen << 1, fstring), cleanup(1);

    source ? (where = cipher) : (where = plain);

    if (read(fd, where, flen) != flen)
        fprintf(stderr, "File read error at %s, exiting\n",
        fstring), cleanup(1);

    for (here = where + flen; --here >= where;)
        if (!*here) {
            fprintf(stderr,
                "File not changed: %s (contains NULL bytes)\n",
                fstring);
            return(3);
        }

    return(0);
}

writefile(source, outflag)
int source, outflag;
{
    long lseek();

    if (fd > 2)
        lseek(fd, 0L, 0);

    switch (source) {
        case NONE:
            break;

        case PLAIN:
            if (outflag) {
                if (write(SCREEN, plain, flen) != flen)
                    fprintf(stderr, "Can't write to stdout, exiting\n"),
                        cleanup(1);
            }
            else if (write(fd, plain, flen) != flen)
                fprintf(stderr, "Can't write file %s, exiting\n",
                    fstring), cleanup(1);
            break;

        case CIPHER:
            if (write(fd, cipher, flen) != flen)
                fprintf(stderr, "Can't write file %s, exiting\n",
                    fstring), cleanup(1);
            break;

        case SMALLBUF:
            if (read(fd, smallbuf, flen) != flen)
                fprintf(stderr, "File read error at %s, exiting\n",
                fstring), cleanup(1);
            *(smallbuf + flen) = '\0';
            if (write(SCREEN, smallbuf, flen) != flen)
                fprintf(stderr, "Can't write to stdout, exiting\n",
                    fstring), cleanup(1);
            break;
    }

    if (fd > 2) {
        close(fd);
        fd = -1;
    }

    if (context != EDMIX || !unphase)
        unlock();

    if (plain != NULL)
        free(plain);
    if (cipher != NULL)
        free(cipher);
}

/* refill chain links with original key values */
rekey()
{

    register char *here;
    int regi;

    here = kstring + klen;
    for (regi = klen; --regi;)
        key[regi].val = *--here;
    key[0].val = *--here;
}

/*  mix:
   given key byte ASCII values "a b c ... n" in circularly linked list,
   and plaintext char pointer ptr initially at plaintext[0],

        advance ptr "a" bytes;
        install its value as the first byte of cyphertext;
        zero out plaintext byte pointed to by ptr;
        decrement value of "a";

        while (not done) {
            advance to next link of key;
            advance ptr by the value of the link;
            install ptr value as the next byte of cyphertext;
            zero out plaintext byte pointed to by ptr;
            decrement value of link;
        }

    Throughout, plaintext is assumed to be a circular list of bytes:
    ptr increments beyond the buffer continue from the beginning.
    If ptr value is null (byte already assigned to cyphertext), ptr is
    incremented until the next non-null plaintext byte.
    When key link value reaches zero, the just assigned plaintext byte
    is swapped into its position.
*/

mix()
{
    register struct keychar *K;
    register char *here, *cip;
    int regi;

    rekey();

    head = plain;
    here = head + flen -1;
    tail = here -1;
    loop = head - tail -1; /* always negative */
    cip = cipher;
    K = key;

    for (regi = flen -1; --regi >= 0; ) {
        if ((here = here + K->val) > tail)
            here += loop;
        while (!*here)
            if(++here > tail)
            here = head;
        if (!--K->val)
            K->val = *here;
        K = K->next;
        *cip++ = *here;
        *here = 0;
    }
    *cip = *++tail;
}

unmix()
{
    register struct keychar *K;
    register char *here, *cip;
    int regi;

    rekey();

    head = plain;
    K = key;
    here = head + K->val;
    tail = head + flen -2;
    loop = head - tail -1; /* always negative */
    cip = cipher;

    for (regi = flen -1; --regi >= 0; ) {
        while (*here)
            if(++here > tail)
            here = head;
        *here = *cip++;
        if (!--K->val)
            K->val = *here;
        K = K->next;
        if ((here = here + K->val) > tail)
            here += loop;
    }
    *(tail +1) = *cip;
}

//E*O*F mix.c//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
     156    1004    6337 mix.man
     682    2630   19458 mix.c
     838    3634   25795 total
!!!
wc  mix.man mix.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0
-- 
        Istvan Mohos
        {ihnp4,decvax,allegra}!philabs!hhb!istvan
        HHB Systems 1000 Wyckoff Ave. Mahwah NJ 07430 201-848-8000
====================================================================