485 lines
11 KiB
C
485 lines
11 KiB
C
/* $NetBSD: tftp.c,v 1.4 1996/02/02 18:06:23 mycroft Exp $ */
|
|
|
|
/*
|
|
* source in this file came from
|
|
* the Mach ethernet boot written by Leendert van Doorn.
|
|
*
|
|
* Trivial File Transfer Protocol (see RFC 783).
|
|
*
|
|
* Copyright (c) 1992 by Leendert van Doorn
|
|
*/
|
|
|
|
#include "proto.h"
|
|
#include "assert.h"
|
|
#include <sys/param.h>
|
|
#include "packet.h"
|
|
#include "ether.h"
|
|
#include "inet.h"
|
|
#include "tftp.h"
|
|
#include "arp.h"
|
|
|
|
ipaddr_t tftp_server; /* IP address of TFTP server */
|
|
ipaddr_t tftp_gateway;
|
|
static char tftp_file_name[100];
|
|
static short block; /* current block */
|
|
static int ctid, stid; /* UDP client and server TID (network order) */
|
|
|
|
extern u_long work_area_org;
|
|
|
|
/*
|
|
* Print IP address in a readable form
|
|
*/
|
|
void
|
|
IpPrintAddr(ipaddr_t addr) {
|
|
inetaddr_t ip;
|
|
|
|
ip.a = addr;
|
|
printf("%d.%d.%d.%d", ip.s.a0, ip.s.a1, ip.s.a2, ip.s.a3);
|
|
}
|
|
|
|
/*
|
|
* Generic TFTP error routine
|
|
*/
|
|
static void
|
|
TftpFail(ipaddr_t fromaddr, ipaddr_t toaddr, char *filename, char *reason) {
|
|
printf("Tftp of file '%s' from ", filename);
|
|
IpPrintAddr(fromaddr);
|
|
printf(" failed, %s\n", reason);
|
|
}
|
|
|
|
/*
|
|
* One complement check sum
|
|
*/
|
|
static u_short
|
|
InChecksum(char *cp, u_long count) {
|
|
u_short *sp;
|
|
u_long sum, oneword = 0x00010000;
|
|
|
|
for (sum = 0, sp = (u_short *)cp, count >>= 1; count--; ) {
|
|
sum += *sp++;
|
|
if (sum >= oneword) {
|
|
/* wrap carry into low bit */
|
|
sum -= oneword;
|
|
sum++;
|
|
}
|
|
}
|
|
return ~sum;
|
|
}
|
|
|
|
/*
|
|
* Setup the standard IP header fields for a destination,
|
|
* and send packet (possibly using the gateway).
|
|
*/
|
|
void
|
|
IpSend(packet_t *pkt, ipaddr_t dst, ipaddr_t gateway) {
|
|
iphdr_t *ip;
|
|
u_char edst[ETH_ADDRSIZE];
|
|
static int ipid = 0;
|
|
#if TRACE > 0
|
|
DUMP_STRUCT("IpSend: pkt (front)", pkt, 100);
|
|
#endif
|
|
pkt->pkt_offset -= sizeof(iphdr_t);
|
|
pkt->pkt_len += sizeof(iphdr_t);
|
|
ip = (iphdr_t *) pkt->pkt_offset;
|
|
ip->ip_vhl = (IP_VERSION << 4) | (sizeof(*ip) >> 2);
|
|
ip->ip_tos = 0;
|
|
ip->ip_len = htons(pkt->pkt_len);
|
|
ip->ip_id = ipid++;
|
|
ip->ip_off = 0;
|
|
ip->ip_ttl = IP_FRAGTTL;
|
|
ip->ip_p = IP_PROTO_UDP;
|
|
ip->ip_src = ip_myaddr ? ip_myaddr : IP_ANYADDR;
|
|
ip->ip_dst = dst;
|
|
ip->ip_sum = 0;
|
|
ip->ip_sum = InChecksum((char *)ip, sizeof(*ip));
|
|
#if 0
|
|
/* DUMP_STRUCT("pkt (after)", pkt, 100); */
|
|
DUMP_STRUCT("ip", ip, sizeof(iphdr_t)+pkt->pkt_len);
|
|
#endif
|
|
if (ArpResolve(pkt, gateway ? gateway : dst, edst)) {
|
|
EtherSend(pkt, ETHTYPE_IP, edst);
|
|
PktRelease(pkt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* States which TFTP can be in
|
|
*/
|
|
enum TftpPacketStatus {
|
|
TFTP_RECD_GOOD_PACKET,
|
|
TFTP_RECD_BAD_PACKET,
|
|
TFTP_RECD_SERVER_ABORT,
|
|
};
|
|
|
|
/*
|
|
* Pseudo header to compute UDP checksum
|
|
*/
|
|
struct pseudoheader {
|
|
ipaddr_t ph_src;
|
|
ipaddr_t ph_dst;
|
|
u_char ph_zero;
|
|
u_char ph_prot;
|
|
u_short ph_length;
|
|
};
|
|
|
|
/*
|
|
* Determine whether this IP packet is the TFTP data packet
|
|
* we were expecting. When a broadcast TFTP request was made
|
|
* we'll set the TFTP server address as well.
|
|
*/
|
|
static enum TftpPacketStatus
|
|
TftpDigestPacket(packet_t *pkt, char *rbuf, u_long *rlen) {
|
|
iphdr_t *ip;
|
|
udphdr_t *up;
|
|
tftphdr_t *tp;
|
|
struct pseudoheader ph;
|
|
u_short oldsum, sum;
|
|
u_short udplength;
|
|
|
|
/* check for minimum size tftp packet */
|
|
if (pkt->pkt_len < (sizeof(ethhdr_t) + sizeof(iphdr_t) +
|
|
sizeof(udphdr_t) + sizeof(tftphdr_t))) {
|
|
#if 0
|
|
printe("TftpDigestPacket: bad packet size %d\n", pkt->pkt_len);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
/* IP related checks */
|
|
ip = (iphdr_t *) (pkt->pkt_offset + sizeof(ethhdr_t));
|
|
if (tftp_server != IP_BCASTADDR && ip->ip_src != tftp_server) {
|
|
#if 0
|
|
printe("TftpDigestPacket: incorrect ip source address 0x%x\n", ip->ip_src);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
if (ntohs(ip->ip_len) <
|
|
sizeof(iphdr_t) + sizeof(udphdr_t) + sizeof(tftphdr_t)) {
|
|
#if 0
|
|
printe("TftpDigestPacket: bad ip length %d\n", ip->ip_len);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
if (ip->ip_p != IP_PROTO_UDP) {
|
|
#if 0
|
|
printe("TftpDigestPacket: wrong ip protocol type 0x%x\n", ip->ip_p);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
if (ip_myaddr && ip->ip_dst != ip_myaddr) {
|
|
#if 0
|
|
printe("TftpDigestPacket: incorrect ip destination address %x\n", ip->ip_dst);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
/* UDP related checks */
|
|
up = (udphdr_t *) ((char *)ip + sizeof(iphdr_t));
|
|
if (block && up->uh_sport != stid) {
|
|
#if 0
|
|
printe("TftpDigestPacket: wrong udp source port 0x%x\n", up->uh_sport);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
*rlen = ntohs(up->uh_len) - sizeof(udphdr_t) - sizeof(tftphdr_t);
|
|
if (up->uh_dport != ctid) {
|
|
#if 0
|
|
printe("TftpDigestPacket: wrong udp destination port 0x%x\n", up->uh_dport);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
/* compute UDP checksum if any */
|
|
oldsum = up->uh_sum;
|
|
if (oldsum) {
|
|
udplength = ntohs(up->uh_len);
|
|
/*
|
|
* zero the byte past the last data byte because the
|
|
* checksum will be over an even number of bytes.
|
|
*/
|
|
if (udplength & 01)
|
|
((char *)up)[udplength] = '\0';
|
|
|
|
/* set up the pseudo-header */
|
|
ph.ph_src = ip->ip_src;
|
|
ph.ph_dst = ip->ip_dst;
|
|
ph.ph_zero = 0;
|
|
ph.ph_prot = ip->ip_p;
|
|
ph.ph_length = htons(udplength);
|
|
|
|
up->uh_sum = ~InChecksum((char *)&ph, sizeof(ph));
|
|
sum = InChecksum((char *)up, (u_long)((udplength + 1) & ~1));
|
|
up->uh_sum = oldsum; /* put original back */
|
|
if (oldsum == (u_short) -1)
|
|
oldsum = 0;
|
|
if (sum != oldsum) {
|
|
#if 0
|
|
printe("TftpDigestPacket: Bad checksum %x != %x, length %d from ",
|
|
sum, oldsum, udplength);
|
|
IpPrintAddr(ip->ip_src);
|
|
printe("\n");
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
}
|
|
|
|
/* TFTP related checks */
|
|
tp = (tftphdr_t *) ((char *)up + sizeof(udphdr_t));
|
|
switch (ntohs(tp->th_op)) {
|
|
case TFTP_ERROR:
|
|
printf("Diagnostic from server: error #%d, %s\n",
|
|
ntohs(tp->th_code), &tp->th_msg);
|
|
return TFTP_RECD_SERVER_ABORT;
|
|
case TFTP_DATA:
|
|
break;
|
|
default:
|
|
#if 0
|
|
printe("TftpDigestPacket: incorrect tftp packet type 0x%x\n", tp->th_op);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
/* reject old packets */
|
|
if (ntohs(tp->th_block) != block + 1) {
|
|
#if 0
|
|
printe("TftpDigestPacket: bad block no. %d\n", tp->th_block);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
/* some TFTP related check */
|
|
if (block == 0) {
|
|
stid = up->uh_sport;
|
|
/* in case of a broadcast, remember server address */
|
|
if (tftp_server == IP_BCASTADDR) {
|
|
tftp_server = ip->ip_src;
|
|
#if 0
|
|
printe("Found TFTP server at ");
|
|
IpPrintAddr(tftp_server);
|
|
printe("\n");
|
|
#endif
|
|
}
|
|
}
|
|
if (stid != up->uh_sport) {
|
|
#if 0
|
|
printe("TftpDigestPacket: incorrect udp source port 0x%x\n", up->uh_sport);
|
|
#endif
|
|
return TFTP_RECD_BAD_PACKET;
|
|
}
|
|
|
|
bcopy(&tp->th_data, rbuf, *rlen);
|
|
|
|
/* advance to next block */
|
|
block++;
|
|
return TFTP_RECD_GOOD_PACKET;
|
|
}
|
|
|
|
enum TftpStatus {
|
|
TFTP_SUCCESS,
|
|
TFTP_FAILURE,
|
|
};
|
|
|
|
static enum TftpStatus
|
|
Tftp(char *rbuf, u_long *rlen) {
|
|
u_long time, current, timeout;
|
|
int retry, quit;
|
|
enum TftpStatus rc = TFTP_FAILURE;
|
|
|
|
*rlen = 0;
|
|
timeout = 4; /* four seconds */
|
|
for (retry=0, quit=0; ++retry < NRETRIES && !quit; ) {
|
|
/*
|
|
* Send out a TFTP request. On the first block (actually
|
|
* zero) we send out a read request. Every other block we
|
|
* just acknowledge.
|
|
*/
|
|
packet_t *pkt;
|
|
ethhdr_t *ep;
|
|
udphdr_t *up;
|
|
tftphdr_t *tp;
|
|
#if TRACE > 0
|
|
printe("Tftp: block %d, try #%d\n", block, retry);
|
|
#endif
|
|
pkt = PktAlloc(sizeof(ethhdr_t) + sizeof(iphdr_t));
|
|
up = (udphdr_t *) pkt->pkt_offset;
|
|
tp = (tftphdr_t *) (pkt->pkt_offset + sizeof(udphdr_t));
|
|
if (block == 0) { /* <RRQ> | <filename> | 0 | "octet" | 0 */
|
|
char *cp, *p;
|
|
|
|
tp->th_op = htons(TFTP_RRQ);
|
|
cp = tp->th_stuff;
|
|
for (p = tftp_file_name; *p; )
|
|
*cp++ = *p++;
|
|
*cp++ = '\0';
|
|
*cp++ = 'o';
|
|
*cp++ = 'c';
|
|
*cp++ = 't';
|
|
*cp++ = 'e';
|
|
*cp++ = 't';
|
|
*cp++ = '\0';
|
|
pkt->pkt_len = sizeof(udphdr_t) + (cp - (char *)tp);
|
|
} else { /* else <ACK> | <block> */
|
|
tp->th_op = htons(TFTP_ACK);
|
|
tp->th_block = htons(block);
|
|
#if 0
|
|
printe("ack block %x %x\n", tp->th_block, block);
|
|
#endif
|
|
pkt->pkt_len = sizeof(udphdr_t) + sizeof(tftphdr_t);
|
|
}
|
|
up->uh_sport = ctid;
|
|
up->uh_dport = stid;
|
|
up->uh_sum = 0;
|
|
up->uh_len = htons(pkt->pkt_len);
|
|
#if 0
|
|
DUMP_STRUCT("tftphdr_t", tp, sizeof(tftphdr_t));
|
|
DUMP_STRUCT("udphdr_t", up, sizeof(udphdr_t));
|
|
printe("Tftp: ");
|
|
#endif
|
|
IpSend(pkt, tftp_server, tftp_gateway);
|
|
|
|
/*
|
|
* Receive TFTP data or ARP packets
|
|
*/
|
|
time = timer() + timeout;
|
|
do {
|
|
pkt = EtherReceive();
|
|
if (pkt) {
|
|
static int spin = 0;
|
|
ep = (ethhdr_t *) pkt->pkt_offset;
|
|
#if 0
|
|
DUMP_STRUCT("ethhdr_t", ep, sizeof(ethhdr_t));
|
|
#endif
|
|
switch (ntohs(ep->eth_proto)) {
|
|
case ETHTYPE_ARP:
|
|
ArpInput(pkt);
|
|
break;
|
|
case ETHTYPE_IP:
|
|
switch (TftpDigestPacket(pkt, rbuf, rlen)) {
|
|
case TFTP_RECD_GOOD_PACKET:
|
|
if (block % 8 == 0)
|
|
printf("%c\b", "-\\|/"[spin++ % 4]);
|
|
#if 0
|
|
DUMP_STRUCT("good tftp packet", pkt, 100);
|
|
printe("TBD - copy tftp packet #%d, len %d to buffer\n", block, *rlen);
|
|
#endif
|
|
rc = TFTP_SUCCESS;
|
|
quit = 1;
|
|
break;
|
|
case TFTP_RECD_SERVER_ABORT:
|
|
TftpFail(tftp_server, ip_myaddr, tftp_file_name, "aborted by server");
|
|
|
|
rc = TFTP_FAILURE;
|
|
quit = 1;
|
|
break;
|
|
default:
|
|
/* for anything else, retry */
|
|
#if 0
|
|
printe("Tftp: bogus IP packet rec'd, still waiting\n");
|
|
#endif
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
#if 0
|
|
printe("Tftp: undesired ethernet packet (type 0x%x) rec'd, still waiting\n",
|
|
ep->eth_proto);
|
|
#endif
|
|
break;
|
|
}
|
|
PktRelease(pkt);
|
|
}
|
|
current = timer();
|
|
HandleKbdAttn();
|
|
} while (current < time && !quit);
|
|
|
|
#if 0
|
|
/* TBD - move */
|
|
eth_reset();
|
|
#endif
|
|
|
|
if (current >= time)
|
|
timeout <<= 1;
|
|
}
|
|
|
|
if (retry > NRETRIES) {
|
|
TftpFail(tftp_server, ip_myaddr, tftp_file_name, "timed Out");
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int tftp_at_eof = 1;
|
|
static u_long tftp_unread_bytes_in_buffer = 0;
|
|
|
|
void
|
|
SetTftpParms(ipaddr_t server, ipaddr_t gateway, char *file_name) {
|
|
block = 0;
|
|
strncpy(tftp_file_name, file_name, MAX_FILE_NAME_LEN);
|
|
tftp_server = server;
|
|
tftp_at_eof = 0;
|
|
tftp_unread_bytes_in_buffer = 0;
|
|
stid = htons(IP_PORT_TFTP);
|
|
ctid = htons(rand());
|
|
printf("Attempting to tftp file '%s'", tftp_file_name);
|
|
if (tftp_server != IP_BCASTADDR) {
|
|
printf(" from server ");
|
|
IpPrintAddr(tftp_server);
|
|
} else
|
|
printf(" using IP broadcast");
|
|
tftp_gateway = gateway;
|
|
if (tftp_gateway) {
|
|
printf(" using gateway ");
|
|
IpPrintAddr(tftp_gateway);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
u_long
|
|
Read(void *result, u_long n_req) {
|
|
static u_long bufp = 0;
|
|
static char buf[PKT_DATASIZE];
|
|
u_long length;
|
|
u_long n_recd = 0;
|
|
while (n_req && !tftp_at_eof) {
|
|
if (tftp_unread_bytes_in_buffer) {
|
|
*((char *)result)++ = buf[bufp++];
|
|
n_req--;
|
|
n_recd++;
|
|
tftp_unread_bytes_in_buffer--;
|
|
} else {
|
|
switch (Tftp(buf, &length)) {
|
|
case TFTP_SUCCESS:
|
|
tftp_unread_bytes_in_buffer = length;
|
|
bufp = 0;
|
|
if (length < SEGSIZE)
|
|
tftp_at_eof = 1;
|
|
break;
|
|
default:
|
|
/* anything else should cause this to abend */
|
|
tftp_unread_bytes_in_buffer = 0;
|
|
tftp_at_eof = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return n_recd;
|
|
}
|
|
|
|
u_long
|
|
PhysRead(u_long addr, u_long n_req) {
|
|
u_long n_recd = 0;
|
|
while (n_req) {
|
|
char buf[512];
|
|
u_long nd = n_req<sizeof(buf) ? n_req : sizeof(buf);
|
|
u_long nr = Read(buf, nd);
|
|
if (nr == 0) {
|
|
/* problem, incomplete read */
|
|
break;
|
|
}
|
|
PhysBcopy(LA(buf), addr, nr);
|
|
n_req -= nr;
|
|
n_recd += nr;
|
|
addr += nr;
|
|
}
|
|
return n_recd;
|
|
}
|