# /* User interface to the TFTP daemon. Writes the appropriate * request file and hangs around to report status. Attempts to do * all possible error checking. * * Called by: * * tftp - {} * * where operation can be from/read/get or send/to/write/put, and mode is * an option mode argument. Note that name recognition is done on the * host name, so you need only type enough to make it recognizeable. * defaults to image on a certain machines, (all those that * have UNIX standard file format for their text files) and defaults * to netascii on all others. * * Noel Chiappa MIT-LCS-CSR July 27 1979. * */ #include /* Size on an IN name in bytes */ #define INSZ 4 /* Format of host tables */ extern struct mach { char *m_name; int m_id; } mach[]; extern struct mchid { char m_addr[INSZ]; int m_type; } mchid[]; extern int nmach; /* A few permanent constants */ #define GET 0 #define PUT 1 #define READ GET #define WRITE PUT char rd[] "read"; char wr[] "write"; /* Some known constants. The signal to wake up the daemon will * not change, but the UID etc (used in protection checking * might. The local host address is pretty well constant; * the MYHST is just there for completeness. */ #define RQSIG 1 #define DMGID 13 #define DMUID 18 #define MYNET 022 #define MYSNET 010 #define MYRSD 00 #define MYHOST 010 /* Likewise for the location of various files. */ char lock[] "/usr/martin/tftp/dmdir/lock"; char fnm[] "/usr/martin/tftp/dmdir/rtftpaXXXXX"; char rfnm[] "/usr/martin/tftp/dmdir/RtftpaXXXXX"; /* Termination code table. NTCS is used by bounds checking later in the * program, it is in the decl to keep you honest. */ #define NTCS 22 char *tcs[NTCS] { "Transfer done", "Bad request packet", "Unused", "Bad DATA packet", "Bad DACK packet", "Foreign server reported error", "Unused", "Unused", "Unused", "Unused", "Couldn't open data file", "Bad request file", "Protocol violation", "Foreign host not responding", "Got ERROR in response to last DATA pkt", "Format error in user request file", "Unable to open local data file", "Unused", "Local file being read is too large", "Bad transfer mode specified", "Unknown foreign user in mail", "Bad foreign tid or host for established transfer" }; #define HSTNR 13 /* Del lcl file if read */ /* Error code table. NERRS is used by bounds checking later in the * program, it is in the decl to keep you honest. */ #define NERRS 6 char *errs[NERRS] { "Not defined", "File not found", "Access violation", "Disc full or allocation exceeded", "Illegal TFTP operation", "Unknown transfer ID" }; #define CERROR 5 /* Code for termination by ERROR packet. */ /* Used for printing hex numbers */ char hexstr[] "0123456789ABCDEF"; /* A few tuneable constants. */ #define BSIZ 128 #define MAXWT 300 /* Maximum time to wait for an xfer */ /* Program storage */ int *gargv; /* Global pointer to argv */ int flag; /* Read or write */ char *mode; /* Transfer mode */ char *mch; /* Machine number - 4 bytes */ int server; /* TFTP Daemon PID */ int file; /* General file chan no */ long size; /* Size (as from tell) */ int tcode; /* Termination code */ int code; /* Error code */ float baud; /* Transfer rate */ struct inode inode; /* Used by various stats */ char buffer[BSIZ]; /* Temp buffer used by randoms */ /* Main does essentially all of the work; it check the args, trys * to figure out if the transfer is legal, gets the machine name, * gets the daemon's PID, creates a request files, wakes the daemon, * waits for the transaction to happen, and prints up the result, * the cleans up and dies. */ main(argc, argv) char *argv[]; { register char *p, *q; int i; long tell(); int die(); gargv = argv; if ((argc != 6) && (argc != 5)) error("Wrong no of args"); else mode = argv[5]; if (argv[1][0] == '-') switch (argv[1][1]) { case 'f': case 'r': case 'g': flag = GET; break; case 't': case 's': case 'w': case 'p': flag = PUT; break; default: error("Bad direction"); } else error("No Direction"); i = getmch(argv[3]); if (argc == 5) { if (i == -1) mode = "netascii"; else { if (mchid[i].m_type) mode = "image"; else mode = "netascii"; } } signal(2, die); /* Check out the access. For sending, merely checks for * existence and readability. For receiving, if it exists, * asks if you wish to overwrite, and if so checks for * writeability, otherwise makes sure daemon can do a * create in the superior directory. * This stuff is no foolproof in that it doesn't check * for directory access all along the line. Maybe this * command should run as the TFTP user, or do a hack like * lpr with the -c option? */ if (flag == PUT) if ((stat(argv[2], &inode) == -1) || saccess(READ)) herror("File unreadable"); if (flag == GET) if (stat(argv[2], &inode) != -1) { printf("\nFile exists. Overwrite:"); read(0, buffer, BSIZ); if (buffer[0] != 'y') exit(-1); if (saccess(WRITE)) herror("File unwriteable"); } else { p = argv[2]; q = buffer; while (*q = *p++) q++; while ((*--q != '/') && (q >= buffer)); *++q = '.'; *++q = '\0'; if ((stat(buffer, &inode) == -1) || saccess(WRITE)) herror("File uncreateable"); } if ((file = open(lock, 0)) == -1) herror("Daemon PID file"); if (read(file, buffer, 6) != 6) herror("Daemon PID file"); server = atoi(buffer); mktemp(fnm); if ((file = creat(fnm, 0666)) == -1) herror("Request file"); if ((file = open(fnm, 02)) == -1) /* Kluge, kluge... You can't */ herror("Request file"); /* read a creat'd file... */ wstrng(file, ((flag == GET) ? rd : wr)); wstrng(file, "\n"); if (argv[2][0] != '/') { getwdir(buffer); wstrng(file, buffer); wstrng(file, "/"); } wstrng(file, argv[2]); wstrng(file, "\n"); whexa(file, mch); wstrng(file, "!"); wstrng(file, argv[4]); wstrng(file, "\n"); wstrng(file, mode); wstrng(file, "\n"); size = tell(file); mktemp(rfnm); if (link(fnm, rfnm) == -1) herror("Request file"); if (kill(server, RQSIG) == -1) herror("No server"); /* The strategy here is to wait till the files increases in * size. You eventually give up if it doesn't. The you parse * in until you hit the =, then get the code and print a * suitable message. If it was an error transaction, print * the error, if the foreign server has something to * say, print that too. We print the file name of the request * for ease of use with logs in the background. */ for (i = 0; i < MAXWT; i++) { sleep(1); if (seek(file, 0, 2) == -1) herror("Request file"); if (tell(file) != size) { if (seek(file, ((int) size), 0) == -1) herror("Request file"); if (read(file, buffer, BSIZ) == -1) herror("Request file"); q = buffer; while (*q++ != '='); tcode = atoi(q); if (tcode >= NTCS) herror("Impossible termination code"); printf("\ntftp:\t%s --> %s\n", argv[2], tcs[tcode]); if (tcode == CERROR) { while (*q++ != '\n'); if (*q != 'E') herror("Bad request file format"); while (*q++ != '='); code = atoi(q); if (code >= NERRS) herror("Impossible error code"); printf("tftp:\tError - %s", errs[code]); while (*q++ != '='); for (p = q; *p != '\n'; p++); *p = '\0'; printf("\tMessage - %s\n", q); } if ((tcode == HSTNR) && (flag == GET)) if (unlink(argv[2]) == -1) herror("Trying to delete null file"); if (tcode != 0) break; if (stat(argv[2], &inode) == -1) herror("Can't size local file"); size = ((((long) inode.size0) << 16) + inode.size1); printf("\t%D bytes in %d seconds:", size, i); size =<< 3; baud = ((float) size)/((float) i); printf(" %6.0f baud\n", baud); break; } } if (i == MAXWT) herror("Transfer seems hung"); if (unlink(fnm) == -1) herror("Request file"); return(tcode); } /* Given a snatch of a name, get the canonical name. * The way this works is that it scans down the table * of nicknames; on each pass it includes one further * letter in its comparison. The number of partial * matches is noted on each pass; if there were more * than one, consider one more letter in the comparrison * and go around again. If there was only one, then that * one was it. If there were none, we couldn't recognize * it. * It returns the number of the entry if it found one, * and sets the variable mch to be a pointer to the * machine's number. */ getmch(name) char *name; { register char *q, *p; register i; int j, nfnd, fnd, pass; if ((*name >= '0') && (*name <= '7')) return(getomch(name)); if (*name == '#') return(gethmch(name)); for (pass = 1; ; pass++) { nfnd = 0; for (j = 0; j < nmach; j++) { p = name; q = mach[j].m_name; for (i = 0; i < pass; i++) if (*p++ != *q++) break; if (i == pass) { fnd = j; nfnd++; } } if (nfnd > 1) continue; if (nfnd == 0) herror("Unrecognized machine"); if (nfnd == 1) { fnd = mach[fnd].m_id; mch = &mchid[fnd].m_addr; return(fnd); } herror("Impossible error"); } } /* Parse a host number given as a string of hex digits */ gethmch(name) register char *name; { register char *addr, *tmp; register i; addr = alloc(INSZ); mch = addr; name++; tmp = name; for (i = 0; i < 8 ; i++) { if ((*tmp >= '0') && (*tmp <= '9')) { *tmp++ =- '0'; continue; } if ((*tmp >= 'A') && (*tmp <= 'F')) { *tmp++ =- ('A' - 10); continue; } if ((*tmp >= 'a') && (*tmp <= 'f')) { *tmp++ =- ('a' - 10); continue; } herror("Bad char in address string"); } for (i = 0; i < INSZ; i++) *addr++ = ((*name++ << 4) + *name++); return(-1); } /* Parse a host number given as a list of octal numbers */ getomch(name) register char *name; { register char *addr; register i, j; char tmp[INSZ]; addr = alloc(INSZ); mch = addr; addr[0] = MYNET; addr[1] = MYSNET; addr[2] = MYRSD; addr[3] = MYHOST; for (i = 0; i < INSZ; i++) { tmp[i] = atoo(name); while ((*name >= '0') && (*name <= '7')) name++; if (*name == 0) break; if (*name == ',') name++; } if (i == INSZ) herror("Address string malformed"); for (j = 3; j >= 0; j--) { addr[j] = tmp[i--]; if (i < 0) break; } return(-1); } /* Reads an octal number */ atoo(p) register char *p; { register n; n = 0; while(*p >= '0' && *p <= '7') n = ((n << 3) + (*p++ - '0')); return(n); } /* Given a mode, this routine attempts to see if the TFTP daemon * would have that access to the inode stored in 'inode'. It * checks the owner/group bits if they're applicable, otherwise * uses the world ones. */ saccess(mode) { if (inode.uid == DMUID) if (inode.flags & ((mode == READ) ? IOREAD : IOWRITE)) return(0); if (inode.gid == DMGID) if (inode.flags & ((mode == READ) ? IGREAD : IGWRITE)) return(0); if (inode.flags & ((mode == READ) ? IEREAD : IEWRITE)) return(0); else return(1); } /* Prints a string on the given I/O channel till it sees * a null. */ wstrng(file, str) register char *str; { while (*str != '\0') write(file, str++, 1); } /* Prints a hex InterNet address on the given I/O channel. */ whexa(file, num) register char *num; { register tmp, cnt; char buf[2]; for (cnt = 0; cnt < INSZ; cnt++) { tmp = *num++; buf[1] = hexstr[(tmp & 017)]; buf[0] = hexstr[((tmp >> 4) & 017)]; write(file, &buf, 2); } } /* Abort - unlink request file and die */ die() { unlink(fnm); exit(-1); } /* Print suitable error messages. */ error(str) char *str; { printf("\ntftp: %s\n\nusage: tftp - {mode}\n", str); exit(-1); } herror(str) char *str; { printf("\ntftp:\t%s --> %s\n", gargv[2], str); exit(-1); }