sampson@attctc.Dallas.TX.US (Steve Sampson) (07/22/89)
This is a dump to the Minix group. It's where I'm going to start with a uucp mail machine, hopefully someone can use this. It worked in a slightly different version on a UniFLEX machine. It contains the docs that some were looking for. It's kinda verbose and should provide some porting help for 68K machines (It worked on a 68020). I had to do some masking and such to insure certain things worked. See the lowest level and the checksum routine for examples. You may want to just take parts and chuck the rest, as maybe the UUPC might be easier. At any rate it should be of interest to some for ideas. The only thing I wish the UUPC upload did was have everything reference to Ver 1.3. It left a lot of work for 1.3 people. Tough cookies eh? #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # readme # mail.c # rmail.c # uucp.h # uucico.c # uuxqt.c # uucp1.doc # uucp2.doc # This archive created: On Earth as in Heaven cat << \SHAR_EOF > readme Unix(tm) Mail Machine Version 2.4, August 1989 S. R. Sampson This archive of files is the basis for sending mail using the uucp 'G' Protocol. It is based on the DCP program by Richard Lamb as posted on Usenet. The files are: README This file Makefile ## NOT INCLUDED (BROKE) ## uucp.h Header file for Operating System specifics mail.c Remote/Local mail front-end rmail.c Remote mail work file generator (simple bang mode) lmail Local mail handler uucico.c G Protocol dialup machine uuxqt.c Work file execution machine .Systems A sample dialing directory .Devices A sample dialing port setup .Config A sample configuration setup Put the files in a source code directory and simply execute 'make'. This will almost completely build the mail machine. You will have to manually edit the /etc/log/password file to change the default shell to '/etc/uucico'. Then run 'password uucp' and assign the account a password. Following that, find a Unix machine to talk to and setup the .Config, .Systems, and .Devices files for your serial ports. The format for the .Devices file is: /dev/ttyxx Baudrate Type Status Where: xx is the TTY number Baudrate is the setting of the port Type is MODEM or DIRECT Status is FREE or USED You initially create the file with everything FREE and have it set up like so: /dev/tty02 1200 MODEM FREE /dev/tty03 2400 DIRECT FREE The format for the .Systems file is: Machine-Name Call-Time Baudrate Type Dialup-Sequence Where: Machine-name is 6 character significance Call-Time is Any (Not implemented) Baudrate is the machines (or your modems) maximum speed Type is MODEM or DIRECT which is used when looking for a port Dialup-Sequence is a send/receive sequence for logging on The format for the .Config file is: User-Name (The owner of the files (eg. 'uucp')) Machine-Name (6 character significance (eg. 'test')) Error-Name (Who gets mail when problems are found (eg. 'root', 'system')) The uucp directory will contain all the work files to be sent and received. The log file will contain information written by the programs as they run. This information can be useful when debugging. The file is recreated each time and not appended to. Need to fix this when more than one uucico is running at the same time!! This version will automatically run in the slave mode upon login to the uucp account. IT WON'T TAKE MULTIPLE SLAVE LOGINS YET!! An example cron entry to run uucico in the master mode with a debug level 9 would look like so: 0 * * * * /etc/uucico master 9 If you have problems check on all permissions and ownership: /usr/bin/mail system rwxr-x /usr/bin/lmail system rwxr-x SUID /usr/bin/rmail uucp rwxr-x SUID /etc/uucico uucp rwx--- SUID /etc/uuxqt uucp rwx--- SUID /gen/spooler/uucp uucp drwxr-x /gen/spooler/uucp/.Log uucp drwx--- /gen/spooler/uucp/.Systems uucp rw---- /gen/spooler/uucp/.Devices uucp rw---- /gen/spooler/uucp/.Config uucp rw---- Sometimes the .Devices file may become corrupt. Make sure you view it for problems such as no FREE device, or missplaced writes. To send remote mail try something like: mail myconnect!nextconnect!... This is a test . <- '.' or EOF and mail will be spooled. Where myconnect is the name of the Unix(tm) machine you dial up. /* EOF */ SHAR_EOF cat << \SHAR_EOF > mail.c /* * mail.c * * Version 2.4, August 1989 * Public Domain by S. R. Sampson * * This program checks for local or remote indication * and then sends mail using rmail or lmail. */ /* Defines */ #define FALSE 0 #define TRUE ~FALSE /* Includes */ #include "uucp.h" #include <string.h> #include <stdio.h> #include <errno.h> #include <time.h> #include <pwd.h> /* Globals */ long stamp; char dfile[140], from[16], tmp[140]; int remote; /* The program */ main(argc, argv) int argc; char *argv[]; { register FILE *fdfile; register char *ptr; struct passwd *name; if (argc == 1) { execl("/usr/bin/lmail", "mail", (char *)NULL); exit(1); } strcpy(dfile, "/tmp/msXXXXXX"); mktemp(dfile); if ((fdfile = fopen(dfile, "w")) == (FILE *)NULL) { perror("mail 1"); exit(1); } /* * If address has a '!' character flag address as remote * else local. */ if ((ptr = strchr(argv[1], '!')) == (char *)NULL) remote = FALSE; else remote = TRUE; /* * Find out who we are and generate the required * "From name date" line */ if (remote) { name = getpwuid(getuid()); strcpy(from, name->pw_name); endpwent(); time(&stamp); sprintf(tmp, "From %s %s", from, ctime(&stamp)); fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } /* * Copy stdin to a /tmp file */ for (;;) { if (fgets(tmp, sizeof tmp, stdin) == (char *)NULL) break; if (strlen(tmp) == 1 && tmp[0] == '.') break; fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } fflush(fdfile); fclose(fdfile); if (remote) sprintf(tmp, "rmail %s <%s >/dev/null", argv[1], dfile); else sprintf(tmp, "lmail %s <%s >/dev/null", argv[1], dfile); if (system(tmp) != 0) { fprintf(stderr, "mail: Fatal - Could not exec() mailer\n"); exit(1); } #ifndef DEBUG unlink(dfile); #endif } /* EOF */ SHAR_EOF cat << \SHAR_EOF > rmail.c /* * rmail.c * * Mini Remote Mail Handler * Version 2.4, Public Domain August 1989 * * This program is executed by either 'mail' or 'uuxqt'. * * The standard mail doesn't handle remote addresses so I made a * new program called mail and renamed the old mail to lmail. Now * if the new mail finds a remote address it calls rmail, else lmail. * * Usage: * rmail remotesystem!remoteuser myname (test myname) * rmail remotesystem!remoteuser (.Config myname) * rmail localuser * * Limitation: * Will only do bang '!' addresses. */ /* Defines */ #define FALSE 0 #define TRUE ~FALSE /* Includes */ #include "uucp.h" #include <sys/modes.h> #include <sys/dir.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <time.h> #include <pwd.h> /* Globals */ extern char *tzname[2]; /* contains two strings CDT or CST */ extern int daylight; /* array index for above */ struct passwd *names; FILE *fbfile, *fcfile, *fdfile; long stamp; int remote; char bfile[32], cfile[32], dfile[32], xfile[32]; char rmtname[16], rmtuser[128], myname[16], user[16]; char datetime[48], ofrom[256], from[256], tmp[256], sys[32]; char errorname[8], *p, *ptr, *mfgets(), *fgets(); /* The program */ main(argc, argv) int argc; char *argv[]; { if (argc < 2 || argc > 3) { fprintf(stderr, "Usage: rmail [remote!]user [testname]\n"); exit(1); } /* * Read Configuration file */ if ((fcfile = fopen(CONFIG, "r")) != (FILE *)NULL) { mfgets(user, sizeof user, fcfile); mfgets(myname, sizeof myname, fcfile); mfgets(errorname, sizeof errorname, fcfile); fclose(fcfile); } else exit(1); if (argc == 3) /* useful for debuging */ strcpy(myname, argv[2]); /* * If address has a '!' character flag address as remote * else local. */ if ((ptr = strchr(argv[1], '!')) == (char *)NULL) remote = FALSE; else { *ptr = '\0'; remote = TRUE; } /* * "Should" change file creation mask to rwx------ */ umask(S_IOREAD | S_IOWRITE | S_IOEXEC); /* * Change to working directory */ chdir(SPOOLDIR); /* * Calculate the time */ time(&stamp); strcpy(datetime, ctime(&stamp)); datetime[strlen(datetime) - 1] = '\0'; strcat(datetime, " "); strcat(datetime, tzname[daylight]); /* add in CDT or CST */ /* * See if this mail was generated locally * or came from somewhere else. Local mail * has a "From x y" header, remote has a * "From x y remote from z" header. */ strcpy(from, ""); strcpy(ofrom, ""); fgets(tmp, sizeof tmp, stdin); /* get a line from stdin */ /* * First line should be a From line. Our goal is to change from this: * * From a!b (date) remote from c * * To this: * * From c!a!b (date) remote from d * * Alternatively we may get a line from mail: * * From a (date) * * Now we must add on the machine name: * * From a (date) remote from b * * No 'from line' causes an error */ if (strncmp(tmp, "From ", 5) != 0 && strncmp(tmp, ">From ", 6) != 0) { strcpy(dfile, "/tmp/rmXXXXXX"); mktemp(dfile); if ((fdfile = fopen(dfile, "w")) == (FILE *)NULL) { perror("rmail 1"); exit(1); } /* write out old 'from' line */ fwrite(tmp, sizeof(char), strlen(tmp), fdfile); /* * Copy file from stdin to the temporary file */ for (;;) { if (fgets(tmp, sizeof tmp, stdin) == (char *)NULL) break; fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } fflush(fdfile); fclose(fdfile); /* * Send a letter to manager about this */ SendMail("No From Line\n\n", TRUE); exit(0); } /* * Get the (now) garbage "From" into sys * and the remote address into ofrom */ sscanf(tmp, "%s %s ", sys, ofrom); p = tmp; /* * Parse line to seek out "remote from" */ for (;;) { p = strchr(p + 1, 'r'); if (p == NULL) { /* * You get here after parsing to the end of the string * and didn't find "remote from" text. The only other * option is the basic "From" from mail. */ break; } if (strncmp(p, "remote from ", 12) == 0) break; } if (p == NULL) strcat(from, ofrom); else { sscanf(p, "remote from %s", from); strcat(from, "!"); strcat(from, ofrom); } if (remote) { /* spool a remote file */ strcpy(rmtname, argv[1]); strcpy(rmtuser, ptr + 1); sprintf(dfile, "D.%.6sXXXXXX", myname); mktemp(dfile); if ((fdfile = fopen(dfile, "w")) == (FILE *)NULL) { perror("rmail 2"); exit(1); } sprintf(tmp, "From %s %s remote from %s\n", from, datetime, myname); /* put out new from */ fwrite(tmp, sizeof(char), strlen(tmp), fdfile); sprintf(tmp, "Received: by %s on %s\n", myname, datetime); fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } else { /* set up a local file */ strcpy(dfile, "/tmp/rmXXXXXX"); mktemp(dfile); if ((fdfile = fopen(dfile, "w")) == (FILE *)NULL) { perror("rmail 3"); exit(1); } fwrite(">", sizeof(char), 1, fdfile); fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } /* * uuxqt and mail will call with: * "rmail [remote!]user <D.xxx >/dev/null" * * Copy the stdin to the spool or /tmp file */ for (;;) { if (fgets(tmp, sizeof tmp, stdin) == (char *)NULL) break; fwrite(tmp, sizeof(char), strlen(tmp), fdfile); } if (!remote) { fflush(fdfile); fclose(fdfile); /* * See if destination user name exists on this machine */ names = getpwnam(argv[1]); endpwent(); if (names == (struct passwd *)NULL) { SendMail("Unknown User\n\n", FALSE); exit(0); } else sprintf(tmp, "mail %s <%s", argv[1], dfile); if (system(tmp) != 0) execerr(); #ifndef DEBUG unlink(dfile); #endif } else { fclose(fdfile); /* * Now forward the mail, if user does not exist * or the remote machine is not known * then send mail to system manager and sender * * The senders name is now in the global from[] string * * See if we talk to requested remote machine */ if (CheckLegalName(rmtname) == FALSE) { SendMail("Unknown Machine\n\n", FALSE); exit(0); } /* make the spool files for uucico */ sprintf(bfile, "B.%.6sXXXXXX", rmtname); mktemp(bfile); if ((fbfile = fopen(bfile, "w")) == (FILE *)NULL) { perror("rmail 4"); exit(1); } sprintf(tmp, "U %s %s\nF %s\nI %s\nC rmail %s\n", user, myname, dfile, dfile, rmtuser); fwrite(tmp, sizeof(char), strlen(tmp), fbfile); fclose(fbfile); sprintf(xfile, "X.%.6sXXXXXX", rmtname); sprintf(cfile, "C.%.6sXXXXXX", myname); mktemp(xfile); mktemp(cfile); if ((fcfile = fopen(cfile, "w")) == (FILE *)NULL) { perror("rmail 5"); exit(1); } sprintf(tmp,"S %s %s %s - %s 0666\nS %s %s %s - %s 0666\n", dfile, dfile, user, dfile, bfile, xfile, user, bfile); fwrite(tmp, sizeof(char), strlen(tmp), fcfile); fclose(fcfile); } } execerr() { fprintf(stderr, "rmail: Fatal - Could not exec() mailer\n"); exit(1); } /* * Send mail to system manager upon errors * * Mail is contained in a file referenced * by Global variable 'dfile' */ SendMail(str, mgronly) char *str; int mgronly; { strcpy(cfile, "/tmp/rmtXXXXXX"); mktemp(cfile); fcfile = fopen(cfile, "w"); fdfile = fopen(dfile, "r"); strcpy(tmp, "Subject: "); strcat(tmp, str); fwrite(tmp, sizeof(char), strlen(tmp), fcfile); while (fgets(tmp, sizeof tmp, fdfile) != (char *)NULL) { fwrite("> ", sizeof(char), 2, fcfile); fwrite(tmp, sizeof(char), strlen(tmp), fcfile); } fflush(fcfile); fclose(fcfile); fclose(fdfile); #ifndef DEBUG unlink(dfile); #endif /* * Return mail to system manager * (and sender if mgronly == FALSE) */ if (!mgronly) { sprintf(tmp, "mail %s <%s >/dev/null", from, cfile); if (system(tmp) != 0) execerr(); } sprintf(tmp, "mail %s <%s >/dev/null", errorname, cfile); if (system(tmp) != 0) execerr(); #ifndef DEBUG unlink(cfile); #endif } /* * Check the machine name by reading the SYSTEMS file * * returns FALSE if not found, else TRUE * */ CheckLegalName(name) register char *name; { register FILE *fd; char line[132], tmp[16]; if ((fd = fopen(SYSTEMS, "r")) == (FILE *)NULL) return(FALSE); while (fgets(line, sizeof line, fd) != NULL) { sscanf(line, "%s ", tmp); if (strncmp(name, tmp, 6) == 0) { fclose(fd); return(TRUE); } } fclose(fd); return(FALSE); } /* * mfgets (modified fgets) * * Same as fgets() only this version deletes '\n' */ char *mfgets(s, n, iop) register char *s; register int n; register FILE *iop; { register int c; register char *cs; cs = s; while (--n > 0 && (c = getc(iop)) != EOF) { if (c == '\n') { *cs = '\0'; break; } else *cs++ = c; } return((c == EOF && cs == s) ? (char *)NULL : s); } /* EOF */ SHAR_EOF cat << \SHAR_EOF > uucp.h /* * uucp.h * * Header file for Unix(tm) mail machine */ /* * Spool Directory */ #define SPOOLDIR "/usr/spool/uucp" /* * Configuration file (in spooldir directory) format * * 1. user uucp * 2. myname test * 3. errorname system */ #define CONFIG "/usr/spool/uucp/.Config" /* * Systems file (in spooldm2directory) format * * 1. rmtname ihnp4 * 2. cctime 2300-2359 * 3. ttype MODEM * 4. tspeed 2400 * 5. loginseq xx gin: uucp\n sword: secret\n */ #define SYSTEMS ".Systems" /* file with calling info */ /* * Devices file (in spooldir directory) format * * 1. Name /dev/ttyxx * 2. Max-Speed 75 - 19200 Baud * 3. Type MODEM or DIRECT * 4. Status USED or FREE */ #define DEVICES ".Devices" /* file with terminal info */ /* * Error log files (relative to spooldir) */ #define SYSLOG ".Log/uucico" /* error log file for uucico */ #define XQTLOG ".Log/uuxqt" /* error log file for uuxqt */ /* EOF */ SHAR_EOF cat << \SHAR_EOF > uucico.c /* * uucico.c * * Copy In - Copy Out 'G' Protocol * * Version 2.4, August 1989 * * Based on dcp.c Copyright (c) 1985, 1986, 1987 by Richard H. Lamb * Changes are Public Domain by S. R. Sampson * * Called by 'cron', 'at', or 'uucp login'. * * Define DEBUG to prevent unlinking of C. files */ /* Includes */ #include "uucp.h" #include <sys/signal.h> #include <sys/sgtty.h> #include <sys/modes.h> #include <sys/dir.h> #include <string.h> #include <setjmp.h> #include <errno.h> #include <ctype.h> #include <stdio.h> #include <time.h> #include <pwd.h> /* General purpose defines */ #define FALSE 0 #define TRUE ~FALSE #define MAXLINE 132 #define MAXLOGTRY 4 #define MSGTIME 20 #define MAXTRY 5 #define PKTTIME 10 #define PKTSIZE 64 #define PKTSIZ2 2 #define HDRSIZE 6 #define WNDSIZE 1 #define DLE 16 /* Main Switching states */ #define INITIAL 000 #define DEVICE 001 #define LOGIN 002 #define HANDSHAKE 003 #define MASTER 004 #define SLAVE 005 #define END 006 #define ABORT 007 /* Master/Slave states */ #define MS_INIT 020 #define MS_SCANDIR 021 #define MS_SEND 022 #define MS_HANGUP 023 #define MS_RECEIVE 024 #define MS_ABORT 025 #define MS_CHKWORK 026 #define MS_END 027 /* Switching States */ #define SS_HEADER 030 #define SS_DATA 031 #define SS_EOF 032 #define SS_END 033 #define SS_ABORT 034 /* * Control Packet Defines * * xxx name yyy * * 001 CLOSE n/a * 002 RJ last correctly received sequence number * 004 RR last correctly received sequence number * 005 INITC window size * 006 INITB data seqment size * 007 INITA window size */ #define DATA 000 /* The packet is data */ #define CLOSE 001 /* CLOSE, Comunications complete */ #define RJ 002 /* RJ Reject, detected an error */ #define RR 004 /* RR Receiver ready, detected no errors */ #define INITC 005 #define INITB 006 #define INITA 007 #define MODEM 000 /* TTY device types */ #define DIRECT 001 /* Global Variables */ struct passwd *pw; struct { int baudrate; char *code; } rates[] = { /* what I use the most */ B300, "300", B1200, "1200", B2400, "2400", B9600, "9600", B19200, "19200", 0, "" }; #define DEFAULT_BAUD B1200 struct sgttyb NewSetting, OldSetting; long Dpos; FILE *fd, *fdC, *log, *input, *output; int type, size, pkrec, pksent, pknerr, master, debug_level; char InPacket[PKTSIZE], OutPacket[PKTSIZE], Filename[MAXLINE], Cfilename[MAXNAMLEN+1], /* defined in dir.h */ Loginseq[64], Rmtname[8], Device[16], myname[8], user[8], speed[8]; char *rcverr[] = { "Input Buffer Empty", "Bad Header", "Packet Timeout", "Checksum Error", "Wrong Packet Size" }; /* Forward Declarations */ unsigned checksum(); char *mfgets(), *gtime(); jmp_buf env; /* * Usage: * uucico mode debug * * Where mode is 'master' or 'slave' * And debug is an integer from 0 to 9 * * Defaults are slave mode and level 0 debug */ main(argc, argv) int argc; char **argv; { register int state; /* * Read Configuration file */ if ((log = fopen(CONFIG, "r")) != (FILE *)NULL) { mfgets(user, sizeof user, log); mfgets(myname, sizeof myname, log); fclose(log); } else exit(1); /* * Change to the spool directory */ chdir(SPOOLDIR); /* * Open the error log file */ log = fopen(SYSLOG, "w"); setbuf(log, (char *)NULL); /* log file should be unbuffered */ /* * Setup defaults */ master = FALSE; debug_level = 0; fd = fdC = (FILE *)NULL; /* * Process the two command line arguments * The first is "master" or "slave", the second * is an integer debug level between 0 and 9. */ if (argc > 1 && strcmp(argv[1], "master") == 0) master = TRUE; if (argc > 2) { debug_level = abs(atoi(argv[2])); if (debug_level > 9) debug_level = 9; } state = INITIAL; while (TRUE) { switch(state) { case INITIAL: if (master) state = GetSystem(); else state = HandShake(); break; case DEVICE: state = GetTTY(); if (state == ABORT) fprintf(log,"Main(): No Device Available %s", gtime()); break; case LOGIN: state = Login(); break; case HANDSHAKE: state = HandShake(); break; case MASTER: state = Master(); break; case SLAVE: state = Slave(); break; case END: state = SendOO(); } if (state == ABORT) break; } if (master) CloseTTY(); fclose(log); /* go execute any transfered work files */ execl("/etc/uuxqt", "uuxqt", (char *)NULL); } /*------------------------- * uucico Subroutines *------------------------- */ GetReply(string) register char *string; { register int i, len; char c[MAXLINE]; len = strlen(string); c[len--] = 0; while (strcmp(string, c) != 0) { for (i = 0; i < len; i++) c[i] = c[i+1]; if (ReadTTY(&c[i], 1, MSGTIME) == 0) { fprintf(log,"GetReply(): Input timed out %s", gtime()); fprintf(log," Wanted %s got %s\n", string, c); return FALSE; } toascii(c[i]); } return TRUE; } /* * mfgets (modified K&R fgets) * * Same as fgets() only this version deletes '\n' */ char *mfgets(s, n, iop) register char *s; register int n; register FILE *iop; { register int c; register char *cs; cs = s; while (--n > 0 && (c = getc(iop)) != EOF) { if (c == '\n') { *cs = '\0'; break; } else *cs++ = c; } return((c == EOF && cs == s) ? (char *)NULL : s); } char *gtime() { long value; time(&value); return(ctime(&value)); } CheckName() { register FILE *fdsys; char line[MAXLINE], tmp[16]; if ((fdsys = fopen(SYSTEMS, "r")) == NULL) { fprintf(log,"CheckName(): Can't open %s %s", SYSTEMS, gtime()); return FALSE; } while (mfgets(line, sizeof line, fdsys) != (char *)NULL) { sscanf(line, "%s ", tmp); if (strncmp(Rmtname, tmp, 6) == 0) { fclose(fdsys); return TRUE; } } fclose(fdsys); fprintf(log, "CheckName(): Unknown system %s attempted login %s", Rmtname, gtime()); return FALSE; } CheckTime(time) register char *time; { return TRUE; } /*------------------------------------------------- * uucico High Level State Switching routines *------------------------------------------------- */ GetSystem() { register FILE *fdsys; char line[MAXLINE], ttype[8], cctime[16]; if ((fdsys = fopen(SYSTEMS, "r")) == NULL) { fprintf(log,"GetSystem(): Can't open %s %s", SYSTEMS, gtime()); return ABORT; } do { if (fgets(line, sizeof line, fdsys) == (char *)NULL) { fclose(fdsys); return ABORT; /* no more systems */ } sscanf(line, "%s %s %s %s %s", Rmtname, cctime, ttype, speed, Loginseq); type = strcmp(ttype, "MODEM") ? DIRECT : MODEM; } while ( !strcmp(cctime, "Slave") || !CheckTime(cctime) ); fclose(fdsys); return DEVICE; /* go get a device and login */ } /* * Find the next device of the type and speed requested * in the DEVICES file */ GetTTY() { register FILE *fdtty; int Type, Used; char line[MAXLINE], Types[8], Useds[8], Bauds[8]; if ((fdtty = fopen(DEVICES, "r+")) == (FILE *)NULL) { fprintf(log,"GetTTY(): Can't open %s %s", DEVICES, gtime()); return ABORT; } do { if (mfgets(line, sizeof line, fdtty) == (char *)NULL) { fprintf(log,"GetTTY(): No devices available %s", gtime()); fclose(fdtty); return ABORT; } sscanf(line, "%s %s %s %s", Device, Bauds, Types, Useds); Type = strcmp(Types, "MODEM") ? DIRECT : MODEM; Used = strcmp(Useds, "FREE"); } while (Type != type || strcmp(Bauds, speed) || Used); /* * This is a hack until I figure out what I'm doing... */ fseek(fdtty, -5L, 1); Dpos = ftell(fdtty); /* Dpos is used in CloseTTY() */ fputs("USED", fdtty); fclose(fdtty); if (OpenTTY() == FALSE) return ABORT; return LOGIN; } Login() { register int k, j, trys; register char *last; char buffer[MAXLINE]; int flag; j = k = 0; /* * First move everything to 'buffer', changing text * newlines to binary */ while(Loginseq[j] != '\0') { if (Loginseq[j] == '\\' && Loginseq[j+1] == 'n') { buffer[k++] = '\n'; j += 2; continue; } buffer[k++] = Loginseq[j++]; } buffer[k] = j = 0; flag = TRUE; /* start by sending */ while (buffer[j]) { k = j; while (buffer[k] != '-' && buffer[k] != '\0') k++; if (buffer[k] == '\0') buffer[k+1] = '\0'; buffer[k] = '\0'; if (flag) { last = &buffer[j]; WriteString(&buffer[j], FALSE); flag = FALSE; } else { trys = 1; while (GetReply(&buffer[j]) == FALSE) { if (trys >= MAXLOGTRY) { fprintf(log,"Login(): Failed login on remote %s", gtime()); return ABORT; } /* try resending the last sequence */ trys++; WriteString(last, FALSE); } flag = TRUE; } j = k + 1; } return HANDSHAKE; } HandShake() { char t1[16], t2[16]; if (master) { if (ReadString(InPacket, MSGTIME) == 0) return ABORT; if (strncmp(InPacket + 6, Rmtname, 6)) { fprintf(log,"HandShake(): System name match error %s", gtime()); return ABORT; } sprintf(OutPacket, "S%.6s", myname); WriteString(OutPacket, TRUE); if (ReadString(InPacket, MSGTIME) == 0) return ABORT; if (strncmp(InPacket + 1, "OK", 2)) { hserr1: fprintf(log,"HandShake(): Don't like name %s",gtime()); return ABORT; } if (ReadString(InPacket, MSGTIME) == 0) return ABORT; if (InPacket[0] != 'P' && strchr(InPacket[1], 'g') == 0) { WriteString("UN", TRUE); hserr2: fprintf(log,"HandShake(): G Protocol not available %s", gtime()); return ABORT; } WriteString("Ug", TRUE); return MASTER; } else { input = stdin; output = stdout; SetTTY(fileno(input), 0); SetTTY(fileno(output), 0); sprintf(OutPacket, "Shere=%.6s", myname); WriteString(OutPacket, TRUE); if (ReadString(InPacket, MSGTIME) == 0) return ABORT; sscanf(InPacket, "S%s %s %s", Rmtname, t1, t2); sscanf(t2, "-x%d", &debug_level); if (CheckName() == FALSE) goto hserr1; WriteString("ROK", TRUE); /* Republic Of Korea */ WriteString("Pg", TRUE); if (ReadString(InPacket, MSGTIME) == 0) return ABORT; if (strcmp(InPacket, "Ug") != 0) goto hserr2; return SLAVE; } } SendOO() { register int i; char msg[MAXLINE]; WriteString("OOOOOO", TRUE); if (ReadString(msg, MSGTIME) == 0) WriteString("OOOOOO", TRUE); if (master) return INITIAL; else return ABORT; } /*--------------------------------------------------- * uucico Medium Level State Switching routines *--------------------------------------------------- */ Master() { register int state; state = MS_INIT; while (TRUE) { switch (state) { case MS_INIT: state = SendInit(); break; case MS_SCANDIR: state = ScanDirectory(); break; case MS_SEND: state = Send(); break; case MS_HANGUP: state = MasterHangup(); break; case MS_RECEIVE: state = Receive(); break; case MS_ABORT: return ABORT; case MS_END: return END; } } } Slave() { register int state; state = MS_INIT; while (TRUE) { switch (state) { case MS_INIT: state = RcvInit(); break; case MS_RECEIVE: state = Receive(); break; case MS_CHKWORK: state = CheckForWork(); break; case MS_HANGUP: state = SlaveHangup(); break; case MS_SCANDIR: state = ScanDirectory(); break; case MS_SEND: state = Send(); break; case MS_ABORT: return ABORT; case MS_END: return END; } } } /*------------------------------------------------ * uucico Low Level State Switching routines *------------------------------------------------ */ Send() { register int state; state = SS_HEADER; while (TRUE) { /* Do this as long as necessary */ switch (state) { case SS_HEADER: if (fd != (FILE *)NULL) { fprintf(log, "Send(): File already open in SS_HEADER %s", gtime()); state = SS_ABORT; } else state = SendFileHeader(); break; case SS_DATA: state = SendData(); break; case SS_EOF: state = SendEof(); break; case SS_END: fclose(fdC); #ifndef DEBUG unlink(Cfilename); #endif fdC = (FILE *)NULL; return MS_SCANDIR; case SS_ABORT: return MS_ABORT; } } } Receive() { register int state; state = SS_HEADER; while (TRUE) { switch (state) { case SS_HEADER: state = RcvFileHeader(); break; case SS_DATA: state = RcvData(); break; case SS_ABORT: return MS_ABORT; case SS_END: return MS_CHKWORK; } } } MasterHangup() { int len; strcpy(OutPacket, "H"); if (SendPacket(OutPacket, PKTSIZE, TRUE) == FALSE) return ABORT; if (RcvPacket(InPacket, &len) == FALSE) return ABORT; if (strncmp(InPacket, "HN", 2) != 0) return MS_RECEIVE; return MS_END; } SlaveHangup() { strcpy(OutPacket, "HY"); SendPacket(OutPacket, PKTSIZE, 2); /* don't wait for Acknowledge */ ClosePacket(); return MS_END; } GetFile(header) register char *header; { register int i; char line[MAXLINE]; if (mfgets(line, sizeof line, fdC) == (char *)NULL) { fprintf(log,"GetFile(): Unexpected End Of File %s", gtime()); return FALSE; } sscanf(&line[2], "%s ", Filename); for (i = 0; line[i]; i++) if (strncmp(&line[i], "066", 3) == 0) break; line[i+4] = '\0'; strcpy(header, line); /* now contains the whole line up past 066x */ return TRUE; } CheckForWork() { register int c; c = ScanDirectory(); if (c == MS_ABORT) return MS_ABORT; if (c == MS_SEND) { strcpy(OutPacket, "HN"); if (SendPacket(OutPacket, PKTSIZE, TRUE) == FALSE) return MS_ABORT; else return MS_SEND; } else /* c == MS_END */ return MS_HANGUP; } ScanDirectory() { register DIR *dir; register struct direct *pdir; strcpy(Cfilename, "C."); strncat(Cfilename, Rmtname, 6); if ((dir = opendir(SPOOLDIR)) == (DIR *)NULL) { fprintf(log, "ScanDirectory(): Could not open %s %s", SPOOLDIR, gtime()); return MS_ABORT; } while ((pdir = readdir(dir)) != (struct direct *)NULL) { if (strncmp(pdir->d_name, Cfilename, MAXNAMLEN) == 0) { strncpy(Cfilename, pdir->d_name,(int)(pdir->d_namlen)); closedir(dir); if (fdC == (FILE *)NULL) { if ((fdC = fopen(Cfilename, "r")) == (FILE *)NULL) { fprintf(log, "ScanDirectory(): Could not open %s %s", Cfilename, gtime()); return MS_ABORT; } } else { fprintf(log, "ScanDirectory(): File already open %s", gtime()); return MS_ABORT; } return MS_SEND; } } closedir(dir); return MS_END; } /*--------------------------------------------------- * uucico G protocol High Level packet routines *--------------------------------------------------- */ SendInit() { if (OpenPacket() == FALSE) return MS_ABORT; else return MS_SEND; } RcvInit() { if (OpenPacket() == FALSE) return MS_ABORT; else return MS_RECEIVE; } SendFileHeader() { int len; char header[MAXLINE]; /* get next file from current work */ if (GetFile(header) == FALSE) return SS_END; /* end sending session */ if ((fd = fopen(Filename, "r")) == NULL) { fprintf(log, "SendFileHeader(): Can't open %s %s", Filename, gtime()); return SS_ABORT; } /* * Send the header */ if (SendPacket(header, PKTSIZE, TRUE) == FALSE) return SS_ABORT; /* * Get the reply from remote */ if (RcvPacket(InPacket, &len) == FALSE) return SS_ABORT; /* * Abort if remote doesn't say yes */ if (strncmp(InPacket, "SY", 2) != 0) { fprintf(log,"SendFileHeader(): Remote refused request %s", gtime()); return SS_ABORT; } /* * Change to DATA state */ return SS_DATA; } RcvFileHeader() { int len; char fromfile[MAXLINE]; if (RcvPacket(InPacket, &len) == FALSE) return SS_ABORT; if (InPacket[0] == 'H') return SS_END; sscanf(&InPacket[2], "%s %s ", fromfile, Filename); if ((fd = fopen(Filename, "w")) == (FILE *)NULL) { fprintf(log, "RcvFileHeader(): Can't create %s %s", Filename, gtime()); strcpy(OutPacket, "SN4"); if (SendPacket(OutPacket, PKTSIZE, TRUE) == FALSE) return SS_ABORT; else return SS_HEADER; } strcpy(OutPacket, "SY"); if (SendPacket(OutPacket, PKTSIZE, TRUE) == FALSE) return SS_ABORT; /* * Change to DATA state */ return SS_DATA; } SendData() { /* * Get data from file */ if ((size = fread(OutPacket, sizeof(char), PKTSIZE, fd)) < 1) return SS_EOF; if (SendPacket(OutPacket, size, FALSE) == FALSE) return SS_ABORT; return SS_DATA; } SendEof() { int len; OutPacket[0] = '\0'; if (SendPacket(OutPacket, 0, FALSE) == FALSE) return SS_ABORT; if (RcvPacket(InPacket, &len) == FALSE) return SS_ABORT; /* rec CY or CN */ fclose(fd); fd = (FILE *)NULL; if (strncmp(InPacket, "CY", 2) != 0) { fprintf(log, "SendEof(): Rcvd %s expected CY %s", InPacket, gtime()); return SS_ABORT; } return SS_HEADER; /* go get the next file to send */ } RcvData() { int len; if (RcvPacket(InPacket, &len) == FALSE) return SS_ABORT; if (len == 0) { fclose(fd); fd = (FILE *)NULL; strcpy(OutPacket, "CY"); if (SendPacket(OutPacket, PKTSIZE, TRUE) == FALSE) return SS_ABORT; return SS_HEADER; } fwrite(InPacket, sizeof(char), len, fd); return SS_DATA; } /*----------------------------------------------------- * uucico G protocol Medium Level packet routines *----------------------------------------------------- */ ClosePacket() { char tmp[PKTSIZE]; spack(CLOSE, 0, 0, 0, tmp); spack(CLOSE, 0, 0, 0, tmp); } /* * INITA - INITC Handshake */ OpenPacket() { register int i, j; unsigned int npkrec, npksent, len; char tmp[PKTSIZE]; pkrec = 0; pksent = 1; pknerr = 0; for (j = INITA; j >= INITC; j--) { for (i = 0; i < MAXTRY; i++) { spack(j, 0, 0, 0, tmp); if (rpack(&npkrec, &npksent, &len, tmp) == j) goto nextinit; } fprintf(log,"OpenPacket(): Could not INIT Handshake %s", gtime()); return FALSE; nextinit:; } return TRUE; } RcvPacket(data, len) register char *data; register int *len; { register int i; unsigned int npkrec, npksent, nlen, nakflg, val; char tmp[PKTSIZE]; nakflg = FALSE; for (i = 0; i < MAXTRY; i++) { switch (val = rpack(&npkrec, &npksent, &nlen, data)) { case DATA: pkrec = (pkrec + 1) % 8; if (npksent != pkrec) break; *len = nlen; if (nakflg) spack(RJ, pkrec, 0, 0, tmp); else spack(RR, pkrec, 0, 0, tmp); return TRUE; case CLOSE: fprintf(log,"RcvPacket(): Rcvd CLOSE from remote %s", gtime()); return FALSE; default: nakflg = TRUE; fprintf(log, "RcvPacket(): rpack() returned %s %s", rcverr[abs(val) - 1], gtime()); } } if (++pknerr >= MAXTRY) { fprintf(log,"RcvPacket(): Too many packet errors %s", gtime()); return FALSE; } return TRUE; } /* * flg = 2 Just send the packet with no wait for ACK. * flg = TRUE Zero out the unused part of the buffer (for "msg" pkts). * flg = FALSE Normal data */ SendPacket(data, len, flg) register char *data; register int len, flg; { register int i; unsigned int nlen, npkrec, npksent, val; char tmp[PKTSIZE]; /* * If Flag set then NULL fill to PKTSIZE */ if (flg) for (i = strlen(data); i < PKTSIZE; i++) data[i] = '\0'; for (i = 0; i < MAXTRY; i++) { spack(DATA, pkrec, pksent, len, data); if (flg == 2) return TRUE; switch (val = rpack(&npkrec, &npksent, &nlen, tmp)) { case RR: case RJ: if (npkrec != pksent) break; /* Retry on wrong Packet Numbers */ pksent = (pksent + 1) % 8; return TRUE; case CLOSE: fprintf(log,"SendPacket(): Rcvd CLOSE from remote %s", gtime()); return FALSE; default: fprintf(log,"SendPacket(): rpack() returned %s %s", rcverr[abs(val) - 1], gtime()); } } if (++pknerr >= MAXTRY) { fprintf(log,"SendPacket(): Too many packet errors %s", gtime()); return FALSE; } return TRUE; } /* * Send a Packet */ spack(type, npkrec, npksent, len, packet) unsigned type, npkrec, npksent, len; char *packet; { unsigned int c, check, i; char pkt[HDRSIZE]; if (len == 0) *packet = '\0'; pkt[0] = DLE; pkt[4] = (type << 3) & 0xFF; type &= 0x07; switch (type) { case CLOSE: break; case RR: case RJ: pkt[4] |= npkrec; break; case INITA: case INITC: pkt[4] |= WNDSIZE; /* window size */ break; case INITB: pkt[4] |= 1; /* segment size (1 = 64) */ break; case DATA: pkt[4] = (0x80 | (npksent << 3) | npkrec) & 0xFF; /* * If packet length is less than 64 * then first byte needs to indicate the * difference. So shift everything right * and put the difference in first byte */ if (c = PKTSIZE - len) { pkt[4] = (pkt[4] | 0x40) & 0xFF; for (i = PKTSIZE - 1; i > 0; i--) packet[i] = packet[i - 1]; packet[0] = c & 0xFF; } } if (type != DATA) { pkt[1] = 9; /* control packet, size = 0 */ check = pkt[4] & 0xFF; } else { pkt[1] = PKTSIZ2; /* data packet, size = 64 */ check = checksum(packet, PKTSIZE) & 0xFFFF; check = (check ^ (pkt[4] & 0xFF)) & 0xFFFF; } check = (0xAAAA - check) & 0xFFFF; pkt[2] = check & 0xFF; pkt[3] = (check >> 8) & 0xFF; pkt[5] = (pkt[1] ^ pkt[2] ^ pkt[3] ^ pkt[4]) & 0xFF; WriteTTY(pkt, HDRSIZE); /* header is 6-bytes long */ if (pkt[1] != 9) WriteTTY(packet, PKTSIZE); /* data is always 64 bytes long */ } /* * Read Packet * * Returns: * +n Okey-Dokey; * -1 Input buffer empty; * -2 Bad header; * -3 Lost packet timeout; * -4 Checksum error; * -5 Wrong packet size */ rpack(npkrec, npksent, len, packet) register unsigned int *npkrec, *npksent, *len; register char *packet; { register unsigned type, check, checkchk; register int i; char c, pkt[HDRSIZE]; c = 0; while ((c & 0x7F) != DLE) if (ReadTTY(&c, 1, PKTTIME) == 0) return(-1); /* input buffer empty */ if (ReadTTY(&pkt[1], HDRSIZE - 1, PKTTIME) < (HDRSIZE - 1)) return(-1); /* input buffer empty */ /* header is 6-bytes long */ if ((pkt[1] ^ pkt[2] ^ pkt[3] ^ pkt[4] ^ pkt[5]) & 0xFF) return(-2); /* bad header */ if ((pkt[1] & 0x7F) == 9) { /* control packet */ *len = 0; type = ((unsigned)pkt[4] >> 3) & 0x7F; *npkrec = pkt[4] & 0x07; *npksent = *packet = check = checkchk = 0; } else { /* data packet */ if (pkt[1] != PKTSIZ2) return(-5); /* can't handle other than size 64 */ type = DATA; c = pkt[4] & 0x3F; *npksent = ((unsigned)c >> 3) & 0x07; *npkrec = c & 0x07; if (ReadTTY(packet, PKTSIZE, PKTTIME) < PKTSIZE) return(-3); /* packet timeout */ /* 64 byte packets even if partial */ check = ((pkt[3] << 8) | (pkt[2] & 0xFF)) & 0xFFFF; checkchk = checksum(packet, PKTSIZE) & 0xFFFF; checkchk = 0xAAAA-(checkchk^((pkt[4] | 0x80) & 0xFF)) & 0xFFFF; if (checkchk != check) return(-4); /* * See if a "short" packet was received * If so, delete the size difference in first byte */ if (pkt[4] & 0x40) { *len = PKTSIZE - *packet; for (i = 0; i < *len; i++) packet[i] = packet[i + 1]; } else *len = PKTSIZE; packet[*len] = '\0'; } return type; } unsigned checksum(data, n) register char *data; register int n; { register unsigned short t; register short sum, x; sum = -1; x = 0; do { if (sum < 0) { sum <<= 1; sum++; } else sum <<= 1; t = sum; sum += (unsigned short)(*data++ & 0xFF); x += sum ^ n; if ((unsigned short)sum <= t) sum ^= x; } while (--n > 0); return (unsigned)sum; } /*-------------------------------------------------- * uucico G protocol Low Level packet routines *-------------------------------------------------- */ WriteString(msg, syn) register char *msg; register int syn; { if (syn) WriteTTY("\020", 1); WriteTTY(msg, strlen(msg)); if (syn) WriteTTY("", 1); } ReadString(msg, seconds) register char *msg; register int seconds; { register int i; char c; do { if (ReadTTY(&c, 1, seconds) == 0) { rperr: fprintf(log, "ReadString(): Input timed out %s", gtime()); return 0; } } while ((c = toascii(c)) != DLE); for (i = 0; i < MAXLINE && c ; i++) { if (ReadTTY(&c, 1, seconds) == 0) goto rperr; if ((c = toascii(c)) == '\n') msg[i] = c = '\0'; else msg[i] = c; } return(strlen(msg)); } /*------------------------------ * uucico TTY I/O routines *------------------------------ */ ReadTTY(data, len, seconds) register char *data; register int len, seconds; { register int count; int ClkInt(); if (setjmp(env)) return 0; signal(SIGALRM, ClkInt); /* execute ClkInt() if alarm */ alarm(seconds); /* goes off */ if ((count = fread(data, sizeof(char), len, input)) < 1) { alarm(0); return 0; } alarm(0); return count; } WriteTTY(data, len) register char *data; register int len; { fwrite(data, sizeof(char), len, output); } ClkInt() { longjmp(env, 1); } OpenTTY() { register int i, baud; i = 0; baud = DEFAULT_BAUD; /* * Check baud rate entry against table * change 'baud' to new code */ do { if (strcmp(speed, rates[i].code) == 0) { baud = rates[i].baudrate; break; } } while (rates[++i].baudrate != 0); input = fopen(Device, "r"); output = fopen(Device, "w"); if (input == NULL || output == NULL) { fprintf(log, "OpenTTY(): Failure opening device %s %s", Device, gtime()); return FALSE; } SetTTY(fileno(input), baud); SetTTY(fileno(output), baud); return TRUE; } CloseTTY() { register FILE *fdtty; stty(fileno(input), &OldSetting); stty(fileno(output), &OldSetting); fclose(input); fclose(output); if ((fdtty = fopen(DEVICES, "r+")) == (FILE *)NULL) { fprintf(log, "CloseTTY(): Can't re-open %s %s", DEVICES, gtime()); return; } fseek(fdtty, Dpos, 0); /* Dpos is computed in GetTTY() */ fputs("FREE", fdtty); fclose(fdtty); } SetTTY(tty, baud) register int tty, baud; { gtty(tty, &OldSetting); /* get the TTY settings */ gtty(tty, &NewSetting); /* copy old settings to new */ NewSetting.sg_speed = D8S1NONE; /* 8 Bits No Parity One Stop */ NewSetting.sg_flag = RAW & ~ECHO; /* no echo and no processing */ if (baud) /* 0 Means Slave Mode */ NewSetting.sg_prot = baud & 0x0F; /* slap in the given baud */ stty(tty, &NewSetting); /* set the TTY new settings */ } /* EOF */ SHAR_EOF cat << \SHAR_EOF > uuxqt.c /* * uuxqt.c * * Command File Execute * Version 2.4 August 1989 * * Based on dcp.c Copyright (c) 1985, 1986, 1987 by Richard H. Lamb * Changes are Public Domain by S. R. Sampson * * This program searches for work files in the spool directory and * executes them. Work files have a X. prefix. * * It is executed by 'uucico', 'cron', or 'at' */ /* Includes */ #include "uucp.h" #include <sys/modes.h> #include <sys/fcntl.h> #include <sys/dir.h> /* defines MAXNAMLEN and directory stuff */ #include <string.h> #include <stdio.h> #include <time.h> #include <pwd.h> /* Defines */ #define MAXLINE 256 /* Globals */ long stamp; char command[MAXLINE], input[MAXLINE], output[MAXLINE], line[MAXLINE]; char Cfilename[MAXNAMLEN+1], filename[MAXNAMLEN+1], ltime[32]; char systemname[16], username[16], user[16]; /* Forward Declarations */ char *mfgets(); main(argc, argv) int argc; char **argv; { register char *p; register FILE *fdC, *log; register DIR *dd; register struct direct *dir; struct passwd *pw; int inull, onull; /* * Read Configuration file */ if ((log = fopen(CONFIG, "r")) != (FILE *)NULL) { mfgets(user, sizeof user, log); fclose(log); } else exit(1); /* * Change to spool directory */ chdir(SPOOLDIR); if ((dd = opendir(SPOOLDIR)) == (DIR *)NULL) exit(1); while ((dir = readdir(dd)) != (struct direct *)NULL) { if (strncmp(dir->d_name, "X.", 2) != 0) continue; strncpy(Cfilename, dir->d_name, dir->d_namlen); if ((fdC = fopen(Cfilename, "r")) == (FILE *)NULL) continue; inull = onull = TRUE; while (mfgets(line, sizeof line, fdC) != NULL) { switch (line[0]) { case 'C': strcpy(command, &line[2]); break; /* * See if all required files are present */ case 'F': strcpy(filename, &line[2]); p = filename; while ((*p != ' ') && *p) p++; *p = '\0'; if (access(filename, 0) == -1) { /* * All files not present, go check * other work files and give up * on this one till next time */ goto not_ready; } break; case 'I': strcpy(input, &line[2]); inull = FALSE; break; case 'O': strcpy(output, &line[2]); onull = FALSE; break; case 'U': strcpy(username, &line[2]); p = username; while ((*p != ' ') && *p) p++; *p++ = '\0'; strcpy(systemname, p); break; } } if (inull) strcpy(input, "/dev/null"); if (onull) strcpy(output, "/dev/null"); sprintf(line,"%s <%s >%s", command, input, output); if ((log = fopen(XQTLOG, "a")) != (FILE *)NULL) { time(&stamp); strcpy(ltime, ctime(&stamp)); ltime[strlen(ltime) - 1] = '\0'; fprintf(log, "%s %s %s %s\n", ltime, systemname, username, line); fclose(log); } if (system(line) != -1) { #ifndef DEBUG unlink(Cfilename); if (!inull) unlink(input); if (!onull) unlink(output); #endif } not_ready: fclose(fdC); } closedir(dd); } /* * mfgets (modified K&R fgets) * * Same as fgets() only this version deletes '\n' */ char *mfgets(s, n, iop) register char *s; register int n; register FILE *iop; { register int c; register char *cs; cs = s; while (--n > 0 && (c = getc(iop)) != EOF) { if (c == '\n') { *cs = '\0'; break; } else *cs++ = c; } return((c == EOF && cs == s) ? (char *)NULL : s); } /* EOF */ SHAR_EOF cat << \SHAR_EOF > uucp1.doc .ce .B Packet Driver Protocol .R .sp 1 .ce G. L. Chesson .br .ce Bell Laboratories .SH Abstract .in +.5i .PP These notes describe the packet driver link protocol that was supplied with the Seventh Edition of .UX and is used by the UUCP program. .in -.5i .SH General .PP Information flow between a pair of machines may be regulated by first representing the data as sequence-numbered .I packets .R of data and then establishing conventions that govern the use of sequence numbers. The .I PK, .R or .I packet driver, .R protocol is a particular instance of this type of flow-control discipline. The technique depends on the notion of a transmission .I window .R to determine upper and lower bounds for valid sequence numbers. The transmitter is allowed to retransmit packets having sequence numbers within the window until the receiver indicates that packets have been correctly received. Positive acknowledgement from the receiver moves the window; negative acknowledgement or no acknowledgement causes retransmission. The receiver must ignore duplicate transmission, detect the various errors that may occur, and inform the transmitter when packets are correctly or incorrectly received. .PP The following paragraphs describe the packet formats, message exchanges, and framing used by the protocol as coded in the UUCP program and the .UX kernel. Although no attempt will be made here to present internal details of the algorithms that were used, the checksum routine is supplied for the benefit of other implementors. .SH Packet Formats .PP The protocol is defined in terms of message transmissions of 8-bit bytes. Each message includes one .I control .R byte plus a .I data segment .R of zero or more information bytes. The allowed data segment sizes range between 32 and 4096 as determined by the formula 32(2\uk\d) where k is a 3-bit number. The packet sequence numbers are likewise constrained to 3-bits; i.e. counting proceeds modulo-8. .PP The control byte is partitioned into three fields as depicted below. .bp .nf .sp .in 1i .ls 1 bit 7 6 5 4 3 2 1 0 t t x x x y y y .ls 1 .in -1i .fi .sp The .I t .R bits indicate a packet type and determine the interpretation to be placed on the .I xxx .R and .I yyy .R fields. The various interpretations are as follows: .in +1i .sp .nf .ls 1 .I tt interpretation .sp .R 00 control packet 10 data packet 11 `short' data packet 01 alternate channel .ls 1 .fi .sp .in -1i A data segment accompanies all non-control packets. Each transmitter is constrained to observe the maximum data segment size established during initial synchronization by the receiver that it sends to. Type 10 packets have maximal size data segments. Type 11, or `short', packets have zero or more data bytes but less than the maximum. The first one or two bytes of the data segment of a short packet are `count' bytes that indicate the difference between the maximum size and the number of bytes in the short segment. If the difference is less than 127, one count byte is used. If the difference exceeds 127, then the low-order seven bits of the difference are put in the first data byte and the high-order bit is set as an indicator that the remaining bits of the difference are in the second byte. Type 01 packets are never used by UUCP and need not be discussed in detail here. .PP The sequence number of a non-control packet is given by the .I xxx .R field. Control packets are not sequenced. The newest sequence number, excluding duplicate transmissions, accepted by a receiver is placed in the .I yyy .R field of non-control packets sent to the `other' receiver. .PP There are no data bytes associated with a control packet, the .I xxx .R field is interpreted as a control message, and the .I yyy .R field is a value accompanying the control message. The control messages are listed below in decreasing priority. That is, if several control messages are to be sent, the lower-numbered ones are sent first. .in +1i .nf .ls 1 .sp .I xxx name yyy .R 1 CLOSE n/a 2 RJ last correctly received sequence number 3 SRJ sequence number to retransmit 4 RR last correctly received sequence number 5 INITC window size 6 INITB data segment size 7 INITA window size .in -i .ls 1 .fi .sp .PP The CLOSE message indicates that the communications channel is to be shut down. The RJ, or .I reject, .R message indicates that the receiver has detected an error and the sender should retransmit after using the .I yyy .R field to update the window. This mode of retransmission is usually referred to as a `go-back-N' procedure. The SRJ, or .I selective reject, .R message carries with it the sequence number of a particular packet to be retransmitted. The RR, or .I receiver ready, .R message indicates that the receiver has detected no errors; the .I yyy .R field updates the sender's window. The INITA/B/C messages are used to set window and data segment sizes. Segment sizes are calculated by the formula 32(2\uyyy\d) as mentioned above, and window sizes may range between 1 and 7. .PP Measurements of the protocol running on communication links at rates up to 9600 baud showed that a window size of 2 is optimal given a packet size greater than 32 bytes. This means that the link bandwidth can be fully utilized by the software. For this reason the SRJ message is not as important as it might otherwise be. Therefore the .UX implementations no longer generate or respond to SRJ messages. It is mentioned here for historical accuracy only, and one may assume that SRJ is no longer part of the protocol. .SH Message Exchanges .SH Initialization .PP Messages are exchanged between four cooperating entities: two senders and two receivers. This means that the communication channel is thought of as two independent half-duplex data paths. For example the window and segment sizes need not be the same in each direction. .PP Initial synchronization is accomplished with two 3-way handshakes: two each of INITA/INITB/INITC. Each sender transmits INITA messages repeatedly. When an INITA message is received, INITB is sent in return. When an INITB message is received .I and .R an INITB message has been sent, an INITC message is sent. The INITA and INITB messages carry with them the packet and window size that each receiver wants to use, and the senders are supposed to comply. When a receiver has seen all three INIT messages, the channel is considered to be open. .PP It is possible to design a protocol that starts up using fewer messages than the interlocked handshakes described above. The advantage of the more complicated design lies in its use as a research vehicle: the initial handshake sequence is completely symmetric, a handshake can be initiated by one side of the link while the connection is in use, and the software to do this can utilize code that would ordinarily be used only once at connection setup time. These properties were used in experiments with dynamically adjusted parameters. That is attempts were made to adapt the window and segment sizes to changes observed in traffic while a link was in use. Other experiments used the initial handshake in a different way for restarting the protocol without data loss after machine crashes. These experiments never worked well in the packet driver and basically provided the impetus for other protocol designs. The result as far as UUCP is concerned is that initial synchronization uses the two 3-way handshakes, and the INIT messages are ignored elsewhere. .SH Data Transport .PP After initial synchronization each receiver sets a modulo-8 incrementing counter R to 0; each sender sets a similar counter S to 1. The value of R is always the number of the most recent correctly received packet. The value of S is always the first sequence number in the output window. Let W denote window size. Note that the value of W may be different for each sender. .PP A sender may transmit packets with sequence numbers in the range S to (S+W-1)\ mod-8. At any particular time a receiver expects arriving packets to have numbers in the range (R+1)\ mod-8 to (R+W)\ mod-8. Packets must arrive in sequence number order are are only acknowledged in order. That is, the `next' packet a receiver will acknowledge must have sequence number (R+1)\ mod-8. .PP A receiver acknowledges receipt of data packets by arranging for the value of its R counter to be sent across the channel where it will be used to update an S counter. This is done in two ways. If data is flowing in both directions across a channel then each receiver's current R value is carried in the .I yyy .R field of non-control packets. Otherwise when there is no bidirectional data flow, each receiver's R value is transmitted across the link as the .I yyy .R field of an RR control packet. .PP Error handling is up to the discretion of the receiver. It can ignore all errors in which case transmitter timeouts must provide for retransmission. The receiver may also generate RJ error control packets. The .I yyy .R field of an incoming RJ message replaces the S value of the local sender and constitutes a request for retransmission to start at that sequence number. The .I yyy .R field of an incoming SRJ message selects a particular packet for retransmission. .PP The resemblance between the flow control procedure in the packet driver and that defined for X.25 is no accident. The packet driver protocol began life as an attempt at cleaning up X.25. That is why, for example, control information is uniform in length (one byte), there is no RNR message (not needed), and there is but one timeout defined in the sender. .SH Termination .PP The CLOSE message is used to terminate communications. Software on either or both ends of the communication channel may initiate termination. In any case when one end wants to terminate it sends CLOSE messages until one is received from the other end or until a programmable limit on the number of CLOSE messages is reached. Receipt of a CLOSE message causes a CLOSE message to be sent. In the .UX environment it also causes the SIGPIPE or `broken pipe' signal to be sent to the local process using the communication channel. .SH Framing .PP The term .I framing .R is used to denote the technique by which the beginning and end of a message is detected in a byte stream; .I error control .R denotes the method by which transmission errors are detected. Strategies for framing and error control depend upon additional information being transmitted along with the control byte and data segment, and the choice of a particular strategy usually depends on characteristics of input/output devices and transmission media. .PP Several framing techniques are in used in support of PK protocol implementations, not all of which can be described in detail here. The technique used on asynchronous serial lines will be described. .PP A six byte framing .I envelope .R is constructed using the control byte C of a packet and five other bytes as depicted below. .in +1i <DLE><k><c0><c1><C><x> .in -1i The <DLE> symbol denotes the ASCII ctrl/P character. If the envelope is to be followed by a data segment, <k> has the value log\d2\u(size)-4; i.e. 1 \(<= k \(<= 8. If k is 9, then the envelope represents a control packet. The <c0> and <c1> bytes are the low-order and high-order bytes respectively of a 16-bit checksum of the data segment, if there is one. For control packets <c1> is zero and <c0> is the same as the control byte C. The <x> byte is the exclusive-or of <k><c0><c1><C>. Error control is accomplished by checking a received framing envelope for compliance with the definition, and comparing a checksum function of the data segment with <c0><c1>. .PP This particular framing strategy assumes data segments are constant-sized: the `unused' bytes in a short packet are actually transmitted. This creates a certain amount of overhead which can be eliminated by a more complicated framing technique. The advantage of this strategy is that i/o devices can be programmed to take advantage of the constant-sized framing envelopes and data segments. .bp .PP The checksum calculation is displayed below as a C function. Note that the code is not truly portable because the definitions of .I short and .I char are not necessarily uniform across all machines that might support this language. This code assumes that .I short and .I char are 16 and 8-bits respectively. .PP .in +.5i .nf .ft CW .ls 1 /* [Original document's version corrected to actual version] */ chksum(s,n) register char *s; register n; { register short sum; register unsigned short t; register short x; sum = -1; x = 0; do { if (sum<0) { sum <<= 1; sum++; } else sum <<= 1; t = sum; sum += (unsigned)*s++ & 0377; x += sum^n; if ((unsigned short)sum <= t) { sum ^= x; } } while (--n > 0); return(sum); } .fi .in -.5i .ft R SHAR_EOF cat << \SHAR_EOF > uucp2.doc I am posting this over the network because I believe that others are interested in knowing the protocols of UUCP. Below is listed all the information that I have acquired to date. This includes the initial handshaking phase, though not the login phase. It also doesn't include information about the data transfer protocol for non-packet networks (the -G option left off the uucico command line). But, just hold on - I am working on that stuff. For a point of information : the slave is the UUCP site being dialed, and the master is the one doing the calling up. The protocols listed in the handshaking and termination phase are independent of any UUCP site : it is universal. The stuff in the work phase depends on the specific protocol chosen. The concepts in the work phase are independent of protocol, ie. the sequences are the same. It is just the lower level stuff that changes from protocol to protocol. I have access only to level g and will document it as I begin to understand it. Most of the stuff you see here is gotten from the debug phase of the current BSD UUCP system. I hope this is useful. Maybe this will get some of the real 'brains' in UUCP to get off their duffs and provide some real detail. In any case, if you have any questions please feel free to contact me. I will post any questions and answers over the network. Chuck Wegrzyn +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ UUCP Handshake Phase ==================== Master Slave ------ ----- <----- \020Shere\0 (1) (2) \020S<mastername> <switches>\0 -----> <----- \020RLCK\0 (3) \020RCB\0 \020ROK\0 \020RBADSEQ\0 <----- \020P<protos>\0 (4) (5) \020U<proto>\0 -----> \020UN\0 (6) ... (0) This communication happens outside of the packet communication that is supported. If the -G flag is sent on the uucico line, all communications will occur without the use of the packet simulation software. The communication at this level is just the characters listed above. (1) The slave sends the sequence indicated, while the master waits for the message. (2) The slave waits for the master to send a response message. The message is composed of the master's name and some optional switches. The switch field can include the following -g (set by the -G switch on the master's uucico command line. Indicates that communication occurs over a packet switch net.) -xN (set by the -x switch on the master's uucico command line. The number N is the debug level desired.) -QM (M is really a sequence number for the communication.) Each switch is separated from the others by a 'blank' character. (3) The slave will send one of the many responses. The meanings appear to be : RLCK This message implies that a 'lock' failure occurred: a file called LCK..mastername couldn't be created since one already exists. This seems to imply that the master is already in communication with the slave. RCB This message will be sent out if the slave requires a call back to the master - the slave will not accept a call from the master but will call the master instead. ROK This message will be returned if the sequence number that was sent in the message, attached to the -Q switch, from the master is the same as that computed on the slave. RBADSEQ Happens if the sequence numbers do not match. (Notes on the sequence number - if a machine does not keep sequence numbers, the value is set to 0. If no -Q switch is given in the master's line, the sequence number is defaulted to 0. The sequence file, SQFILE, has the format <remotename> <number> <month>/<day>-<hour>:<min> where <remotename> is the name of a master and <number> is the previous sequence number. If the <number> field is not present, or if it is greater than 9998, it is set to 0. The <number> field is an ascii representation of the number. The stuff after the <number> is the time the sequence number was last changed, this information doesn't seem important.) (4) The slave sends a message that identifies all the protocols that it supports. It seems that BSD supports 'g' as the normal case. Some sites, such as Allegra, support 'e' and 'g', and a few sites support 'f' as well. I have no information about these protocols. The exact message sent might look like \020Pefg\0 where efg indicates that this slave supports the e,f and g protocols. (5) The slave waits for a response from the master with the chosen protocol. If the master has a protocol that is in common the master will send the message \020U<proto>\0 where <proto> is the protocol (letter) chosen. If no protocol is in common, the master will send the message \020UN\0 (6) At this point both the slave and master agree to use the designated protocol. The first thing that now happens is that the master checks for work. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ UUCP Work Phase =============== Master Slave ------ ----- (a) Master has UUCP Work (1) X file1 file2 -----> <----- XN (2) XY When the master wants the slave to do a 'uux' command it sends the X message. If the slave can't or won't do it, the slave will send an XN message. Otherwise it will send an XY message. (b) Master wants to send a file (1) S file1 file2 user options -----> <----- SN2 (2) SN4 SY <---- <data exchanged>----> (3) <----- CY (4) CN5 If the master wishes to send a file to the slave, it will send a S message to the slave. If the slave can or will do the transfer, it sends a SY message. If the slave has a problem creating work files, it sends a SN4 message. If the target file can't be created (because of priv's etc) it sends a SN2 message. The file1 argument is the source file, and file2 is the (almost) target filename. If file2 is a directory, then the target filename is composed of file2 concatenated with the "last" part of the file1 argument. Note, if the file2 argument begins with X, the request is targeted to UUX and not the normal send. The user argument indicates who, if anyone, is to be notified if the file has been copied. This user must be on the slave system. I am not sure what the options argument does. After the data has been exchanged the slave will send one of two messages to the master. A CY message indicates that every- thing is ok. The message CN5 indicates that the slave had some problem moving the file to it's permanent location. This is not the same as a problem during the exchange of data : this causes the slave to terminate operation. (c) Master wishes to receive a file. (1) R file1 file2 user -----> <----- RN2 (2) RY mode (3) <---- <data exchanged> ----> (4) CY -----> CN5 If the master wishes the slave to send a file, the master sends a R message. If the slave has the file and can send it, the slave will respond with the RY message. If the slave can't find the file, or won't send it the RN2 message is sent. It doesn't appear that the 'mode' field of the RY message is used. The argument file1 is the file to transfer, unless it is a directory. In this case the file to be transferred is built of a concatenation of file1 with the "last" part of the file2 argument. If anything goes wrong with the data transfer, it results in both the slave and the master terminating. After the data has been transferred, the master will send an acknowledgement to the slave. If the transfer and copy to the destination file has been successful, the master will send the CY message. Otherwise it will send the CN5 message. (d) Master has no work, or no more work. (1) H -----> <----- HY (2) HN (3) HY -----> <---- HY (4) (5) ... The transfer of control is initiated with the master sending a H message. This message tells the slave that the master has no work, and the slave should look for work. If the slave has no work it will respond with the HY message. This will tell the master to send an HY message, and turn off the selected protocol. When the HY message is received by the slave, it turns off the selected protocol as well. Both the master and slave enter the UUCP termination phase. If the slave does have work, it sends the HN message to the master. At this point, the slave becomes the master. After the master receives the HN message, it becomes the slave. The whole sequence of sending work starts over again. Note, the transmission of HN doesn't force the master to send any other H messages : it waits for stuff from the new master. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ UUCP Termination Sequence ========================= Master Slave ------ ----- (1) \020OOOOOO\0 -----> <----- \020OOOOOOO\0 (2) At this point all conversation has completed normally. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ UUCP Data Transfers =================== After the initial handshake the systems send messages in one of two styles : packet and not packet. A Packet protocol is just raw data transfers : there is no protocol or acknowledgements; this appears to assume that the lower level is a packet network of some type. If the style is not Packet, then extra work is done. I am still working on this stuff. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** summary of UUCP packets ** note that all transmissions end with a null, not shown here (master) (slave) ... dials up ... <DLE>Shere says "hello" <DLE>S<sysname> <opts> says who he is | <DLE>ROK says ok to talk | <DLE>RLCK says locked out | <DLE>RCB says will call back | <DLE>RBADSEQ says bad seq num <DLE>P<protos> what protocols he has <DLE>U<proto> | which to use <DLE>UN | use none, hang up packet driver is turned on at this time, if not told otherwise -- if master has work -- to send file to slave... S <mfilenm> <sfilenm> <user> <opts> request to send file | SY ok -- i'll take it | SN2 not permitted | SN4 can't make workfile <data> the file is transmitted | CY finished OK | CN5 can't move into place to recv file from slave... R <sfilenm> <mfilenm> <user> request to recv file | RY<mode> ok -- here is prot mode | RN2 not permitted <data> file is transmitted CY | worked CN5 | can't move into place to do UUX on slave... X <file1> <file2> request to exec file | XY ok -- will do | XN nopers to indicate that he has no more work... H no more work | HN reverse roles | HY no work here either to accept slave's claim of no more work... HY agrees to hang up the rest of the hang-up is done OUTSIDE of packet driver <DLE>OOOOOO signs off (6*'O') <DLE>OOOOOOO signs off (7*'O') If the slave has work, then the roles are reversed, and the session proceeds from the label 'loop1' above. The system which was the slave is now the master, and the old master is just the slave. The <opts> which follow the system name for the start-up sequence include: -g don't use packet driver (command line -G) -xN debug level (command line -Xn) -QN seq number (if systems use this) The filenames for <mfilenm> should be complete filenames with path information; otherwise they are assumed to be in /usr/spool/uucp. The filenames for <sfilenm> should be either complete filenames or directory names. If directory names are used, then the final componant of <mfilenm> is appended to form the complete filename. The 'X' command to do UUX on a slave is more than a little unclear. It doesn't seem to work here, but that may be a microsoft "feature". Protocol "g", which seems to be the one most commonly used, is supposed to be a slightly munged version of level 2 of X.25; an article was just posted in net.unix-wizards (which you probably have already seen) to this effect. The article didn't provide any details on the protocol, but merely mentioned the modifications. The "packet" mode, with no protocol, does not work under microsoft implementations, and may have *lots* of trouble working anywhere else as well. It evidently requires that zero-length reads happen every so often to delimit things, such as files being transferred. This of course can't happen without the packet driver, which was long gone by the time sys-3 or sys-5 or <your current version> came along. New Info: I got the answer that version 7 and its descendants use only Shere while SVR2 uses Shere=xxx (or is it the other way around). Anyway, the correct change would be to check if there is a '=' then verify the remote name. /* EOF */ SHAR_EOF # End of shell archive exit 0