/* * cico: talk to uucp machines */ #include int Debug = 0; /* global debugging level */ int remote_debug = 0; /* -x option for other machine */ int Describe=0; /* describe what's going on */ /* * stuff that the uucp protocol needs */ static char outbuf[256]; /* general output buffer */ static char inbuf[256]; /* general input buffer */ static int role = UNKNOWN; /* master mode? */ /* * stuff that's needed to do multi-hop file transfers */ static char rmt_sys[20]; /* remote system name */ static char rmt_file[100]; /* file with system name removed */ static int rmt_seq; /* sequence # of tempfile D.### */ static char rmt_zseq[4]; /* base 36 seq string */ static char is_rmt; /* flag for the outside world... */ jmp_buf Sjbuf; char program[] = "cico"; #if MSDOS /* need to preserve where we started from... */ char currentdir[200]; #endif extern char tty[]; extern int getopt(); extern int optopt, opterr, optind; extern char *optarg; /* * uucico: in the flesh */ main(argc, argv) register argc; register char **argv; { register i; #if MSDOS getcwd(currentdir, 200); #endif while (getopt(argc, argv, "r:PVDX:x:") != EOF) switch (optopt) { case 'r': switch (*optarg) { case '0': role = SLAVE; break; case '1': role = MASTER; break; default: goto badboy; } break; case 'D': Describe = 1; break; case 'P': role = LOCAL; break; case 'V': puts("Cico V1.2"); exit(0); case 'X': remote_debug = atoi(optarg); if (remote_debug < 1) remote_debug = 1; break; case 'x': Debug = atoi(optarg); if (Debug < 1) Debug = 1; break; default: badboy: /* don't you just hate it when people put */ /* gotos into switch statements? */ fprintf(stderr, "usage: cico [-r1] [-VD] [-x#] [-X#] [system ...]\n"); fprintf(stderr, " cico -P [-VD] [-x#] [-X#]\n"); #if MSDOS|GEMDOS|OS2 fprintf(stderr, " cico -r0 [-VD] [-x#] [-X#] ttyname\n"); #else fprintf(stderr, " cico [-r0] [-VD] [-x#] [-X#]\n"); #endif exit(1); } if (Debug) setvbuf(stderr, (char *)0, _IONBF, 1); if (getcfg() == 0) exit(2); if (xchdir(SPOOL) != 0) { fprintf(stderr, "cico: can't find %s\n", SPOOL); exit(3); } switch (role) { case LOCAL: /* cico -P */ process(); break; case MASTER: /* cico -r1 */ if (optind >= argc) { lookforwork(); break; } /* else fall into unknown case, for processing systems */ case UNKNOWN: /* cico [sys...] */ if (optind < argc) { for (i=optind; i= argc) goto badboy; strcpy(callee, argv[optind]); #else strcpy(callee, "tty"); #endif cico(); break; } #if MSDOS xchdir(currentdir); #endif exit(0); } /* * cico: deal with a system */ cico() { char remote[16]; strcpy(remote, callee); if (lock(remote)) { ttyopen(tty, ttyflow); if (setjmp(Sjbuf) == 0 && connected()) { Turnon(); if (role == MASTER) master(); else slave(); Turnoff(); printmsg(Describe?0:6, "%s: Conversation finished", callee); } ttyclose(); process(); /* deal with workfiles that were just put here... */ unlock(remote); } else printmsg(Describe?0:1, "%s: Already talking", callee); unlock(tty); } /* cico */ /* * lookforwork() looks for systems that have work to be done on them. */ lookforwork() { char sysname[9]; FILE *cfg; char line[400]; int cnt; int linenum=0; char foundwork=0; extern char systyp[]; if (cfg = fopen(makepath(line, NETDIR, SYSTEMS), "r")) { while (rdline(cfg, line, sizeof line)) { ++linenum; cnt = sscanf(line, "%s %s %s %ld %s %[^\n]", callee, callwhen, systyp, &callspeed, callphone, callseq); if (cnt <= 1) continue; if (stricmp(systyp, "acu") == 0) calltype = ACU; else if (stricmp(systyp, "dir") == 0) calltype = DIR; else continue; if ((calltype == ACU && cnt != 6) || stricmp(callwhen, "any") != 0) continue; printmsg(Describe?0:8, "Checking %s", callee); if (access(makepath(line, SPOOL, "%.7s", callee), 0) >= 0) { if (findtty(callee)) { role = MASTER; cico(); foundwork=1; } else printmsg(1, "No available device to call %s", callee); } } fclose(cfg); } if (!foundwork) process(); } /* lookforwork */ /* * uucpcfg() tries to set up to call a specific system */ uucpcfg(system) char *system; { if (oksys(system)) { if (findtty() == 0) { logerror("No available devices"); return 0; } else if (stricmp(callwhen, "any") != 0) { logerror("Wrong time to call"); return 0; } } else { printmsg(0, "Unknown system %s", system); return 0; } return 1; } /* *----------------------UUCP PROTOCOL DRIVERS---------------------- */ /* * role_reverse() enslaves the master. */ static role_reverse() { wmesg(HANGUP, ""); if (uucp_msg(HANGUP) == 'Y') return 0; role = SLAVE; return 1; } /* * uucp master mode. */ master() { traffic(); if (role_reverse()) slave(); } /* * uucp slave mode */ slave() { while (grdmsg(inbuf, 0) == SUCCESS) switch (inbuf[0]) { case SEND: getfile(inbuf); break; case REQUEST: putfile(inbuf); break; default: wmesg(inbuf[0], "N3"); break; case HANGUP: traffic(); /* traffic() makes us the master if there is * any traffic, otherwise we can just proceed * to hangup */ if (role != MASTER || !role_reverse()) return 0; break; /* at this point we're the slave again! */ } } /* slave */ /* * putfile() send a requested file to the master [SLAVE mode] */ putfile(pkt) char *pkt; { char from[128]; FILE *takethat; printmsg(Describe?0:1, "slave request (%s)", pkt); if (parsefrom(pkt, from) && (takethat=fopen(from, "rb"))) { wmesg(REQUEST, "Y"); if (gwrdata(takethat) == FAIL || uucp_msg(DONE) != 'Y') logerror("%s: failed (%s)", pkt, 1+inbuf); fclose(takethat); } else wmesg(REQUEST, "N1"); } /* putfile */ /* * getfile() accepts a file from the master [SLAVE mode] */ getfile(pkt) register char *pkt; { char to[128]; FILE *gimme; register status; printmsg(Describe?0:1, "slave send (%s)", pkt); #if 0 status = parseto(pkt, to); printmsg(10, "tempfile %s (status %d)", to, status); if (status && (gimme=fopen(to, "wb"))) { #else if (parseto(pkt, to) && (gimme=fopen(to, "wb"))) { #endif wmesg(SEND,"Y"); status = grddata(gimme); fclose(gimme); if (status != 0) abandon("write error (disk full?)"); else if (spoolout(pkt, to)) wmesg(DONE, "Y"); else { wmesg(DONE, "N5"); remove(to); } } else wmesg(SEND,"N4"); /* cannot create tempfile */ } /* getfile */ /* * spoolout() - if the file we just recieved was destined for a foreign * system, try to send it there. Returns 1 if a local file or we were * able to create the send request, otherwise 0 */ spoolout(command, workfile) register char *command; register char *workfile; { char User[80], Options[40], Notify[80]; int Mode; FILE *wrk; register cnt; if (is_rmt) { /* * if it's a remote file, we need to update the workfile * for the system it's destined for. */ cnt = sscanf(command, "%*s %*s %*s %s %s %*s %o %s", User, Options, &Mode, Notify); /* fill in fields with defaults if the command-line is deficient. */ switch (cnt) { case -1: case 0: strcpy(User, "uucp"); case 1: strcpy(Options, "-"); case 2: Mode = 0644; case 3: strcpy(Notify, "uucp"); } if (wrk=fopen(rmt_sys, "a")) { fprintf(wrk, " %3sS %s %s %s!%s %s %s %03o %s\n", rmt_zseq, workfile, rmt_file, callee, User, Options, workfile, Mode, Notify); fclose(wrk); } else return 0; } return 1; } /* spoolout */ /* * rmesg() gets a message from the other side */ rmesg(c, msg, n) register char *msg, c; register int n; { printmsg(4, "rmesg(%c,%d)", c ? c : '0', n); while (grdmsg(msg, 0) != SUCCESS) { if (--n > 0) { printmsg(7, "Retry %d", n); continue; } printmsg(5, "Bad read: expected %c got FAIL", c ? c : '0'); return FAIL; } if (c && msg[0] != c) { printmsg(5, "Bad read: expected `%c' got %s", c, msg); return FAIL; } printmsg(7, "got %s", msg); return SUCCESS; } /* rmesg */ /* * wmesg() writes a message to the other side */ static wmesg(ctrl, text) register char *text, ctrl; { printmsg(7, "wmesg(%c%s)", ctrl, text); return gwrmsg(ctrl, text); } /* * abandon panics and flees from the connect */ static abandon(msg) char *msg; { logerror(msg); if (Describe) fprintf(stderr, "%s\n", msg); longjmp(Sjbuf, 1); } /* * Turnon() starts up G protocol */ static Turnon() { if (gturnon() != SUCCESS) /* initialize packet protocol */ abandon("Gturnon failure"); } /* * uucp_msg() waits for a specific message, then returns the control byte. */ static uucp_msg(control) /* wait for specified UUCP message */ { if (rmesg(control, inbuf, 5) != SUCCESS) abandon("message wait timeout"); return inbuf[1]; } /* * connected() sets up a connection with a master or slave */ connected() { register char *p, c; register int try; if (role == MASTER) { getallowed(); printmsg(Describe?0:1, "Calling %s on %s (%ld bps)", callee, tty, ttycspeed); flush(); if (calltype != ACU) ttyspeed(ttycspeed); else if ((try=dial(callphone, 5)) != 0) { if (try != 1) logerror("dial failed with status %d", try); return 0; } else printmsg(Describe?0:10, "Carrier detected"); sleep(2); if (login(callseq) == 0) { logerror("login failed"); return 0; } printmsg(10, "Logged in"); if (imsg(inbuf) != SUCCESS) { /* wait for "here" message */ logerror("didn't get Shere"); return 0; } if (strncmp(inbuf, "Shere", 5)) { logerror("Shere garbled: %s", inbuf); return 0; } if (p = strchr(inbuf, '=')) /* get calling system's name */ strcpy(callee, 1+p); /* identify ourself */ omsg(remote_debug ? "S%s -Q0 -x%d" : "S%s -Q0", SITE, remote_debug); /* * See if the other system loves us */ if (imsg(inbuf) != SUCCESS || strcmp(inbuf, "ROK") != 0) { logerror("call refused: %s", 1+inbuf); return 0; } /* * only accept G protocol here */ if (imsg(inbuf) != SUCCESS || !strchr(inbuf, 'g')) { omsg("UN"); /* we can't do any other */ logerror("doesn't know G protocol"); return 0; /* so we don't talk anymore */ } omsg("Ug"); } else { /* * slave - exchange system names, then determine transfer protocol */ omsg("Shere=%s", SITE); if (imsg(inbuf) == SUCCESS) { if (oksys(inbuf)) { omsg("ROK"); } #define anonymous 0 else if (!anonymous) { omsg("RYou must be a space alien"); logerror("Unknown caller"); return 0; } getallowed(); omsg("Ug"); if (imsg(inbuf) != SUCCESS || !strchr(inbuf, 'g')) { logerror("doesn't know G protocol"); return 0; } } } printmsg(Describe?0:5, "%s: Talking (%s mode)", callee, (role==MASTER) ? "master" : "slave"); return 1; } /* * tosify() changes / to \ and wipes out all runs of /. */ char * tosify(s) register char *s; { register char *p, *q; for (p=q=s; *p; ++p) { if (*p == '/') { if (q != s && q[-1] == '\\') continue; *p = '\\'; } *q++ = *p; } *q = 0; return s; } /* * traffic() does the nitty-gritty of master mode. * * If cico is compiled for Bauble, it does some perversions: * before checking the workfile, it runs collect to catch up on mail and * news, then, after working through the workfile and deleting it, it * jumps back to the start of the routine and runs collect _again_ to get * anything it didn't process the first time through (on a floppy-based * system, there are space constraints and collect may take several iterations * to process all the files.) */ traffic() { FILE *work; FILE *thisf; char temp[200]; static char local[128]; static char spool[128]; char opt[40]; register char *file; register long checkpoint; register long here; register count; #ifdef BAUBLE restart: collect(callee); #endif if ((work=fopen(makepath(temp, SPOOL, "%.7s", callee), "r+")) == (FILE *)0) return 0; checkpoint = ftell(work); while (fgets(temp, 128, work)) { strtok(temp, "\n"); if (temp[0] == ' ') { if (role == SLAVE) { /* if we're called from slave mode */ wmesg(HANGUP, "N"); /* we'll need to make ourselves boss */ role = MASTER; /* before we're able to do things */ } switch (temp[4]) { case SEND: count = sscanf(5+temp, "%s %*s %*s %s %s", local, opt, spool); printmsg(Describe?0:1, "master send (%s)", 5+temp); if (count >= 1) { /* * are we working with a file that's been copied to the * spool directory, or just the straight file? */ file = (count == 3 && strchr(opt, 'C')) ? spool : local; tosify(spool); tosify(local); if (thisf=fopen(file, "rb")) { wmesg(SEND, 5+temp); if (uucp_msg(SEND) != 'Y') logerror("%s: (%s)", 4+temp, 1+inbuf); else { gwrdata(thisf); if (uucp_msg(DONE) != 'Y') logerror("%s: (%s)", 4+temp, 1+inbuf); } fclose(thisf); remove(spool); /* always kill the spoolfile, just */ /* in case... */ sleep(2); } } break; case REQUEST: printmsg(Describe?0:1, "master request (%s)", 5+temp); opt[0] = 0; if (sscanf(5+temp, "%*s %s %*s %*s %s", local, opt) >= 1) { /* * request that's gonna end up elsewhere */ if (local[0] == '!' && isremote(local)) sprintf(local, "D%s.%s", rmt_sys, rmt_zseq); else if (strchr(opt, 'f') == 0) walkpath(local); if (thisf=fopen(local, "wb")) { wmesg(REQUEST, 5+temp); if (uucp_msg(REQUEST) != 'Y') { logerror("%s: (%s)", 4+temp, 1+inbuf); fclose(thisf); remove(local); } else { grddata(thisf); fclose(thisf); wmesg(DONE, "Y"); spoolout(4+temp, local); } } } break; } here = ftell(work); fseek(work, checkpoint, SEEK_SET); putc('X', work); fseek(work, here, SEEK_SET); } checkpoint = ftell(work); } fclose(work); remove(makepath(temp, SPOOL, "%.7s", callee)); #ifdef BAUBLE goto restart; #endif } /* * getallowed() gets the allowed directories list for callee */ getallowed() { char line[200]; char dirallowed[200]; char system[20]; FILE *x; if (x=fopen(makepath(line, NETDIR, USERS), "r")) { while (rdline(x, line, 200)) if (sscanf(line, "%s %200[^ ]", system, dirallowed) == 2) if (strncmp(system, callee,7) == 0) { makeallowed(dirallowed); return 0; } fclose(x); } makeallowed(""); } /* getallowed */ /* * walkpath() makes a path */ static walkpath(path) register char *path; { register c; register char *p; if ((p=strrchr(path, '\\')) || (p=strrchr(path,'/'))) {/* pick subdirectories out */ *p = 0; walkpath(path); /* recurse on parent dir */ mkdir(path); /* don't care about errors here... */ *p = '/'; } } /* walkpath */ /* * isremote() - is the file we've given cico a remote file (for master R, * slave S) */ isremote(file) register char *file; { register char *p; register sys_len; extern char *itob36(); ++file; if (p=strchr(file, '!')) { sys_len = (int)(p-file); strncpy(rmt_sys, file, sys_len); rmt_sys[sys_len] = 0; strcpy(rmt_file, ++p); rmt_seq = getseq(); strcpy(rmt_zseq, itob36(rmt_seq)); is_rmt = 1; } else is_rmt = 0; return is_rmt; } /* isremote */ /* * parsefrom() generates a filename for a file request. Only ~/name and * ~name: are expanded. */ parsefrom(reqcmd, dest) char *reqcmd; register char *dest; { char from[128]; if (sscanf(reqcmd, "%*s %s %*s", from) >= 1) { tosify(from); switch (parsegeneric(basename(from), from, dest)) { case 2: return allowed(0,dest); case 1: return 1; } } return 0; } /* * parseto() generates a filename. If the file doesn't have any directory * information assocated with it, it's considered to be a workfile and is * mangled into 8.3 form from 1.12 form; otherwise ~/name, ~name:, and !site * routing is properly dealt with and all other pathnames are rejected. */ parseto(sendcmd, dest) char *sendcmd; register char *dest; { char from[128], to[128], options[40]; register char *rf, *rt; register inspool, canmake, st; is_rmt = 0; options[0] = 0; if (sscanf(sendcmd, "%*s %s %s %*s %s", from, to, options) < 2) return 0; rf = basename(tosify(from)); rt = basename(tosify(to)); canmake = strchr(options, 'f') == 0; if (to[0] == '!') { /* this is an intermediate node */ if (isremote(to)) { sprintf(dest, "D%s.%s", rmt_sys, rmt_zseq); is_rmt = 1; return 1; } return 0; } if (rt == to) { twiddle(to, dest); return 1; } else { st = parsegeneric(rf, to, dest); if (st == 1 || (st == 2 && allowed(1,dest))) { if (canmake) walkpath(dest); return 1; } } return 0; } /* parseto */ /* * twiddle converts a workfile from ?.xxx to xxx.? form. */ twiddle(to, dest) register char *to, *dest; { register size; register char *p; if (to[1] == '.') { size = strlen(p=(2+to)); if (size > 8) /* take low 8 characters of spoolfile */ p += size-8; /* and hope for uniqueness */ makepath(dest, SPOOL, "%s.%c", p, *to); strlwr(dest); } } #if MSDOS|OS2|GEMDOS /* * parsegeneric does ~drive:, ~/subdir conversions */ parsegeneric(from, to, dest) register char *from, *to, *dest; { register st, i; register c; if (to[0] != '~') return 0; else if (isalpha(to[1]) && to[2] == ':') { i = 3; if (to[i] == '\\') ++i; sprintf(dest, "%c:/%s", to[1], i+to); st = 2; } else if (to[1] == '\\') { for (i=2; to[i] && to[i] != '\\'; i++) ; if (to[i]) to[i++] = 0; sprintf(dest, "%s/%s/%s", PUBLIC, to+2, to+i); st = 1; } else return 0; c = EOS(dest)[-1]; if (c == '/' || c == '\\') strcat(dest, from); strlwr(dest); return st; } /* parsegeneric */ #else /* * parsegeneric() does nothing except glue names to /-terminated * destination names */ parsegeneric(from, to, dest) char *from, *to, *dest; { register c; if (*to != '~') return 0; makepath(to, PUBLIC, dest); c = EOS(dest)[-1]; strcpy(to, dest); if (*dest && c == '/') strcat(to, basename(from)); } /* parsegeneric */ #endif /*parsegeneric*/ /* * Turnoff() kills G protocol */ static Turnoff() /* wrap up the call and close the channel */ { wmesg(HANGUP, "Y"); /* tell other side that yes, we are dead */ gturnoff(); if (role == SLAVE) { imsg(inbuf); /* wait for master to -OO- */ omsg("OOOOOOO"); /* then -OO- ourselves */ } else { omsg("OOOOOO"); /* -OO- the slave */ imsg(inbuf); /* wait for his reply */ } } /* * logerror() writes an error message into $NETDIR/cico.log */ logerror(fmt) char *fmt; { va_list ptr; char fname[80]; FILE *log; extern char *istime(); va_start(ptr, fmt); if (log = fopen(makepath(fname, NETDIR, "LOGFILE"), "a")) { fprintf(log, "%s @ %s: ", callee, istime()); vfprintf(log, fmt, ptr); putc('\n', log); fclose(log); } else { fprintf(stderr, "cico: can't open %s\nerror message is:\n %s", fname, callee); vfprintf(stderr, fmt, ptr); putc('\n', stderr); } va_end(ptr); }