596 lines
16 KiB
C
596 lines
16 KiB
C
/******************************************************************************
|
|
* Copyright (c) 2004, 2008 IBM Corporation
|
|
* All rights reserved.
|
|
* This program and the accompanying materials
|
|
* are made available under the terms of the BSD License
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.opensource.org/licenses/bsd-license.php
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial implementation
|
|
*****************************************************************************/
|
|
|
|
#include <tftp.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <ethernet.h>
|
|
#include <ipv4.h>
|
|
#include <ipv6.h>
|
|
#include <udp.h>
|
|
|
|
//#define __DEBUG__
|
|
|
|
#define MAX_BLOCKSIZE 1428
|
|
#define BUFFER_LEN 256
|
|
|
|
#define ENOTFOUND 1
|
|
#define EACCESS 2
|
|
#define EBADOP 4
|
|
#define EBADID 5
|
|
#define ENOUSER 7
|
|
//#define EUNDEF 0
|
|
//#define ENOSPACE 3
|
|
//#define EEXISTS 6
|
|
|
|
#define RRQ 1
|
|
#define WRQ 2
|
|
#define DATA 3
|
|
#define ACK 4
|
|
#define ERROR 5
|
|
#define OACK 6
|
|
|
|
/* Local variables */
|
|
static unsigned char packet[BUFFER_LEN];
|
|
static unsigned char *buffer = NULL;
|
|
static unsigned short block = 0;
|
|
static unsigned short blocksize;
|
|
static char blocksize_str[6]; /* Blocksize string for read request */
|
|
static int received_len = 0;
|
|
static int retries = 0;
|
|
static int huge_load;
|
|
static int len;
|
|
static int tftp_finished = 0;
|
|
static int lost_packets = 0;
|
|
static int tftp_errno = 0;
|
|
static int ip_version = 0;
|
|
static short port_number = -1;
|
|
static tftp_err_t *tftp_err;
|
|
static filename_ip_t *fn_ip;
|
|
|
|
/**
|
|
* dump_package - Prints a package.
|
|
*
|
|
* @package: package which is to print
|
|
* @len: length of the package
|
|
*/
|
|
#ifdef __DEBUG__
|
|
|
|
static void
|
|
dump_package(unsigned char *buffer, unsigned int len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i <= len; i++) {
|
|
printf("%02x%02x ", buffer[i - 1], buffer[i]);
|
|
i++;
|
|
if ((i % 16) == 0)
|
|
printf("\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* send_rrq - Sends a read request package.
|
|
*
|
|
* @fd: Socket Descriptor
|
|
*/
|
|
static void
|
|
send_rrq(int fd)
|
|
{
|
|
int ip_len = 0;
|
|
int ip6_payload_len = 0;
|
|
unsigned short udp_len = 0;
|
|
unsigned char mode[] = "octet";
|
|
char *ptr = NULL;
|
|
struct iphdr *ip = NULL;
|
|
struct ip6hdr *ip6 = NULL;
|
|
struct udphdr *udph = NULL;
|
|
struct tftphdr *tftp = NULL;
|
|
|
|
memset(packet, 0, BUFFER_LEN);
|
|
|
|
if (4 == ip_version) {
|
|
ip = (struct iphdr *) packet;
|
|
udph = (struct udphdr *) (ip + 1);
|
|
ip_len = sizeof(struct iphdr) + sizeof(struct udphdr)
|
|
+ strlen((char *) fn_ip->filename) + strlen((char *) mode) + 4
|
|
+ strlen("blksize") + strlen(blocksize_str) + 2;
|
|
fill_iphdr ((uint8_t *) ip, ip_len, IPTYPE_UDP, 0,
|
|
fn_ip->server_ip);
|
|
}
|
|
else if (6 == ip_version) {
|
|
ip6 = (struct ip6hdr *) packet;
|
|
udph = (struct udphdr *) (ip6 + 1);
|
|
ip6_payload_len = sizeof(struct udphdr)
|
|
+ strlen((char *) fn_ip->filename) + strlen((char *) mode) + 4
|
|
+ strlen("blksize") + strlen(blocksize_str) + 2;
|
|
ip_len = sizeof(struct ip6hdr) + ip6_payload_len;
|
|
fill_ip6hdr ((uint8_t *) ip6, ip6_payload_len, IPTYPE_UDP, get_ipv6_address(),
|
|
&(fn_ip->server_ip6));
|
|
|
|
}
|
|
udp_len = htons(sizeof(struct udphdr)
|
|
+ strlen((char *) fn_ip->filename) + strlen((char *) mode) + 4
|
|
+ strlen("blksize") + strlen(blocksize_str) + 2);
|
|
fill_udphdr ((uint8_t *) udph, udp_len, htons(2001), htons(69));
|
|
|
|
tftp = (struct tftphdr *) (udph + 1);
|
|
tftp->th_opcode = htons(RRQ);
|
|
|
|
ptr = (char *) &tftp->th_data;
|
|
memcpy(ptr, fn_ip->filename, strlen((char *) fn_ip->filename) + 1);
|
|
|
|
ptr += strlen((char *) fn_ip->filename) + 1;
|
|
memcpy(ptr, mode, strlen((char *) mode) + 1);
|
|
|
|
ptr += strlen((char *) mode) + 1;
|
|
memcpy(ptr, "blksize", strlen("blksize") + 1);
|
|
|
|
ptr += strlen("blksize") + 1;
|
|
memcpy(ptr, blocksize_str, strlen(blocksize_str) + 1);
|
|
|
|
send_ip (fd, packet, ip_len);
|
|
|
|
#ifdef __DEBUG__
|
|
printf("tftp RRQ with %d bytes transmitted.\n", ip_len);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* send_ack - Sends a acknowlege package.
|
|
*
|
|
* @blckno: block number
|
|
* @dport: UDP destination port
|
|
*/
|
|
static void
|
|
send_ack(int fd, int blckno, unsigned short dport)
|
|
{
|
|
int ip_len = 0;
|
|
int ip6_payload_len = 0;
|
|
unsigned short udp_len = 0;
|
|
struct iphdr *ip = NULL;
|
|
struct ip6hdr *ip6 = NULL;
|
|
struct udphdr *udph = NULL;
|
|
struct tftphdr *tftp = NULL;
|
|
|
|
memset(packet, 0, BUFFER_LEN);
|
|
|
|
if (4 == ip_version) {
|
|
ip = (struct iphdr *) packet;
|
|
udph = (struct udphdr *) (ip + 1);
|
|
ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + 4;
|
|
fill_iphdr ((uint8_t *) ip, ip_len, IPTYPE_UDP, 0,
|
|
fn_ip->server_ip);
|
|
}
|
|
else if (6 == ip_version) {
|
|
ip6 = (struct ip6hdr *) packet;
|
|
udph = (struct udphdr *) (ip6 + 1);
|
|
ip6_payload_len = sizeof(struct udphdr) + 4;
|
|
ip_len = sizeof(struct ip6hdr) + ip6_payload_len;
|
|
fill_ip6hdr ((uint8_t *) ip6, ip6_payload_len, IPTYPE_UDP, get_ipv6_address(),
|
|
&(fn_ip->server_ip6));
|
|
}
|
|
udp_len = htons(sizeof(struct udphdr) + 4);
|
|
fill_udphdr ((uint8_t *) udph, udp_len, htons(2001), htons(dport));
|
|
|
|
tftp = (struct tftphdr *) (udph + 1);
|
|
tftp->th_opcode = htons(ACK);
|
|
tftp->th_data = htons(blckno);
|
|
|
|
send_ip(fd, packet, ip_len);
|
|
|
|
#ifdef __DEBUG__
|
|
printf("tftp ACK %d bytes transmitted.\n", ip_len);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* send_error - Sends an error package.
|
|
*
|
|
* @fd: Socket Descriptor
|
|
* @error_code: Used sub code for error packet
|
|
* @dport: UDP destination port
|
|
*/
|
|
static void
|
|
send_error(int fd, int error_code, unsigned short dport)
|
|
{
|
|
int ip_len = 0;
|
|
int ip6_payload_len = 0;
|
|
unsigned short udp_len = 0;
|
|
struct ip6hdr *ip6 = NULL;
|
|
struct iphdr *ip = NULL;
|
|
struct udphdr *udph = NULL;
|
|
struct tftphdr *tftp = NULL;
|
|
|
|
memset(packet, 0, BUFFER_LEN);
|
|
|
|
if (4 == ip_version) {
|
|
ip = (struct iphdr *) packet;
|
|
udph = (struct udphdr *) (ip + 1);
|
|
ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + 5;
|
|
fill_iphdr ((uint8_t *) ip, ip_len, IPTYPE_UDP, 0,
|
|
fn_ip->server_ip);
|
|
}
|
|
else if (6 == ip_version) {
|
|
ip6 = (struct ip6hdr *) packet;
|
|
udph = (struct udphdr *) (ip6 + 1);
|
|
ip6_payload_len = sizeof(struct udphdr) + 5;
|
|
ip_len = sizeof(struct ip6hdr) + ip6_payload_len;
|
|
fill_ip6hdr ((uint8_t *) ip6, ip6_payload_len, IPTYPE_UDP, get_ipv6_address(),
|
|
&(fn_ip->server_ip6));
|
|
}
|
|
udp_len = htons(sizeof(struct udphdr) + 5);
|
|
fill_udphdr ((uint8_t *) udph, udp_len, htons(2001), htons(dport));
|
|
|
|
tftp = (struct tftphdr *) (udph + 1);
|
|
tftp->th_opcode = htons(ERROR);
|
|
tftp->th_data = htons(error_code);
|
|
((char *) &tftp->th_data)[2] = 0;
|
|
|
|
send_ip(fd, packet, ip_len);
|
|
|
|
#ifdef __DEBUG__
|
|
printf("tftp ERROR %d bytes transmitted.\n", ip_len);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
print_progress(int urgent, int received_bytes)
|
|
{
|
|
static unsigned int i = 1;
|
|
static int first = -1;
|
|
static int last_bytes = 0;
|
|
char buffer[100];
|
|
char *ptr;
|
|
|
|
// 1MB steps or 0x400 times or urgent
|
|
if(((received_bytes - last_bytes) >> 20) > 0
|
|
|| (i & 0x3FF) == 0 || urgent) {
|
|
if(!first) {
|
|
sprintf(buffer, "%d KBytes", (last_bytes >> 10));
|
|
for(ptr = buffer; *ptr != 0; ++ptr)
|
|
*ptr = '\b';
|
|
printf(buffer);
|
|
}
|
|
printf("%d KBytes", (received_bytes >> 10));
|
|
i = 1;
|
|
first = 0;
|
|
last_bytes = received_bytes;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
/**
|
|
* get_blksize tries to extract the blksize from the OACK package
|
|
* the TFTP returned. From RFC 1782
|
|
* The OACK packet has the following format:
|
|
*
|
|
* +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
|
|
* | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 |
|
|
* +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
|
|
*
|
|
* @param buffer the network packet
|
|
* @param len the length of the network packet
|
|
* @return the blocksize the server supports or 0 for error
|
|
*/
|
|
static int
|
|
get_blksize(unsigned char *buffer, unsigned int len)
|
|
{
|
|
unsigned char *orig = buffer;
|
|
/* skip all headers until tftp has been reached */
|
|
buffer += sizeof(struct udphdr);
|
|
/* skip opc */
|
|
buffer += 2;
|
|
while (buffer < orig + len) {
|
|
if (!memcmp(buffer, "blksize", strlen("blksize") + 1))
|
|
return (unsigned short) strtoul((char *) (buffer +
|
|
strlen("blksize") + 1),
|
|
(char **) NULL, 10);
|
|
else {
|
|
/* skip the option name */
|
|
buffer = (unsigned char *) strchr((char *) buffer, 0);
|
|
if (!buffer)
|
|
return 0;
|
|
buffer++;
|
|
/* skip the option value */
|
|
buffer = (unsigned char *) strchr((char *) buffer, 0);
|
|
if (!buffer)
|
|
return 0;
|
|
buffer++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Handle incoming tftp packets after read request was sent
|
|
*
|
|
* this function also prints out some status characters
|
|
* \|-/ for each packet received
|
|
* A for an arp packet
|
|
* I for an ICMP packet
|
|
* #+* for different unexpected TFTP packets (not very good)
|
|
*
|
|
* @param fd socket descriptor
|
|
* @param packet points to the UDP header of the packet
|
|
* @param len the length of the network packet
|
|
* @return ZERO if packet was handled successfully
|
|
* ERRORCODE if error occurred
|
|
*/
|
|
int32_t
|
|
handle_tftp(int fd, uint8_t *pkt, int32_t packetsize)
|
|
{
|
|
struct udphdr *udph;
|
|
struct tftphdr *tftp;
|
|
|
|
/* buffer is only set if we are handling TFTP */
|
|
if (buffer == NULL )
|
|
return 0;
|
|
|
|
#ifndef __DEBUG__
|
|
print_progress(0, received_len);
|
|
#endif
|
|
udph = (struct udphdr *) pkt;
|
|
tftp = (struct tftphdr *) ((void *) udph + sizeof(struct udphdr));
|
|
set_timer(TICKS_SEC);
|
|
|
|
#ifdef __DEBUG__
|
|
dump_package(pkt, packetsize);
|
|
#endif
|
|
|
|
port_number = udph->uh_sport;
|
|
if (tftp->th_opcode == htons(OACK)) {
|
|
/* an OACK means that the server answers our blocksize request */
|
|
blocksize = get_blksize(pkt, packetsize);
|
|
if (!blocksize || blocksize > MAX_BLOCKSIZE) {
|
|
send_error(fd, 8, port_number);
|
|
tftp_errno = -8;
|
|
goto error;
|
|
}
|
|
send_ack(fd, 0, port_number);
|
|
} else if (tftp->th_opcode == htons(ACK)) {
|
|
/* an ACK means that the server did not answers
|
|
* our blocksize request, therefore we will set the blocksize
|
|
* to the default value of 512 */
|
|
blocksize = 512;
|
|
send_ack(fd, 0, port_number);
|
|
} else if ((unsigned char) tftp->th_opcode == ERROR) {
|
|
#ifdef __DEBUG__
|
|
printf("tftp->th_opcode : %x\n", tftp->th_opcode);
|
|
printf("tftp->th_data : %x\n", tftp->th_data);
|
|
#endif
|
|
switch ( (uint8_t) tftp->th_data) {
|
|
case ENOTFOUND:
|
|
tftp_errno = -3; // ERROR: file not found
|
|
break;
|
|
case EACCESS:
|
|
tftp_errno = -4; // ERROR: access violation
|
|
break;
|
|
case EBADOP:
|
|
tftp_errno = -5; // ERROR: illegal TFTP operation
|
|
break;
|
|
case EBADID:
|
|
tftp_errno = -6; // ERROR: unknown transfer ID
|
|
break;
|
|
case ENOUSER:
|
|
tftp_errno = -7; // ERROR: no such user
|
|
break;
|
|
default:
|
|
tftp_errno = -1; // ERROR: unknown error
|
|
}
|
|
goto error;
|
|
} else if (tftp->th_opcode == DATA) {
|
|
/* DATA PACKAGE */
|
|
if (block + 1 == tftp->th_data) {
|
|
++block;
|
|
}
|
|
else if( block == 0xffff && huge_load != 0
|
|
&& (tftp->th_data == 0 || tftp->th_data == 1) ) {
|
|
block = tftp->th_data;
|
|
}
|
|
else if (tftp->th_data == block) {
|
|
#ifdef __DEBUG__
|
|
printf
|
|
("\nTFTP: Received block %x, expected block was %x\n",
|
|
tftp->th_data, block + 1);
|
|
printf("\b+ ");
|
|
#endif
|
|
send_ack(fd, tftp->th_data, port_number);
|
|
lost_packets++;
|
|
tftp_err->bad_tftp_packets++;
|
|
return 0;
|
|
} else if (tftp->th_data < block) {
|
|
#ifdef __DEBUG__
|
|
printf
|
|
("\nTFTP: Received block %x, expected block was %x\n",
|
|
tftp->th_data, block + 1);
|
|
printf("\b* ");
|
|
#endif
|
|
/* This means that an old data packet appears (again);
|
|
* this happens sometimes if we don't answer fast enough
|
|
* and a timeout is generated on the server side;
|
|
* as we already have this packet we just ignore it */
|
|
tftp_err->bad_tftp_packets++;
|
|
return 0;
|
|
} else {
|
|
tftp_err->blocks_missed = block + 1;
|
|
tftp_err->blocks_received = tftp->th_data;
|
|
tftp_errno = -42;
|
|
goto error;
|
|
}
|
|
tftp_err->bad_tftp_packets = 0;
|
|
/* check if our buffer is large enough */
|
|
if (received_len + udph->uh_ulen - 12 > len) {
|
|
tftp_errno = -2;
|
|
goto error;
|
|
}
|
|
memcpy(buffer + received_len, &tftp->th_data + 1,
|
|
udph->uh_ulen - 12);
|
|
send_ack(fd, tftp->th_data, port_number);
|
|
received_len += udph->uh_ulen - 12;
|
|
/* Last packet reached if the payload of the UDP packet
|
|
* is smaller than blocksize + 12
|
|
* 12 = UDP header (8) + 4 bytes TFTP payload */
|
|
if (udph->uh_ulen < blocksize + 12) {
|
|
tftp_finished = 1;
|
|
return 0;
|
|
}
|
|
/* 0xffff is the highest block number possible
|
|
* see the TFTP RFCs */
|
|
|
|
if (block >= 0xffff && huge_load == 0) {
|
|
tftp_errno = -9;
|
|
goto error;
|
|
}
|
|
} else {
|
|
#ifdef __DEBUG__
|
|
printf("Unknown packet %x\n", tftp->th_opcode);
|
|
printf("\b# ");
|
|
#endif
|
|
tftp_err->bad_tftp_packets++;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
#ifdef __DEBUG__
|
|
printf("\nTFTP errno: %d\n", tftp_errno);
|
|
#endif
|
|
tftp_finished = 1;
|
|
return tftp_errno;
|
|
}
|
|
|
|
/**
|
|
* TFTP: This function handles situation when "Destination unreachable"
|
|
* ICMP-error occurs during sending TFTP-packet.
|
|
*
|
|
* @param err_code Error Code (e.g. "Host unreachable")
|
|
*/
|
|
void
|
|
handle_tftp_dun(uint8_t err_code)
|
|
{
|
|
tftp_errno = - err_code - 10;
|
|
tftp_finished = 1;
|
|
}
|
|
|
|
/**
|
|
* TFTP: Interface function to load files via TFTP.
|
|
*
|
|
* @param _fn_ip contains the following configuration information:
|
|
* client IP, TFTP-server IP, filename to be loaded
|
|
* @param _buffer destination buffer for the file
|
|
* @param _len size of destination buffer
|
|
* @param _retries max number of retries
|
|
* @param _tftp_err contains info about TFTP-errors (e.g. lost packets)
|
|
* @param _mode NON ZERO - multicast, ZERO - unicast
|
|
* @param _blocksize blocksize for DATA-packets
|
|
* @return ZERO - error condition occurs
|
|
* NON ZERO - size of received file
|
|
*/
|
|
int
|
|
tftp(filename_ip_t * _fn_ip, unsigned char *_buffer, int _len,
|
|
unsigned int _retries, tftp_err_t * _tftp_err,
|
|
int32_t _mode, int32_t _blocksize, int _ip_version)
|
|
{
|
|
retries = _retries;
|
|
fn_ip = _fn_ip;
|
|
len = _len;
|
|
huge_load = _mode;
|
|
ip_version = _ip_version;
|
|
tftp_errno = 0;
|
|
tftp_err = _tftp_err;
|
|
tftp_err->bad_tftp_packets = 0;
|
|
tftp_err->no_packets = 0;
|
|
|
|
/* Default blocksize must be 512 for TFTP servers
|
|
* which do not support the RRQ blocksize option */
|
|
blocksize = 512;
|
|
|
|
/* Preferred blocksize - used as option for the read request */
|
|
if (_blocksize < 8)
|
|
_blocksize = 8;
|
|
else if (_blocksize > MAX_BLOCKSIZE)
|
|
_blocksize = MAX_BLOCKSIZE;
|
|
sprintf(blocksize_str, "%d", _blocksize);
|
|
|
|
printf(" Receiving data: ");
|
|
print_progress(-1, 0);
|
|
|
|
// Setting buffer to a non-zero address enabled handling of received TFTP packets.
|
|
buffer = _buffer;
|
|
|
|
set_timer(TICKS_SEC);
|
|
send_rrq(fn_ip->fd);
|
|
|
|
while (! tftp_finished) {
|
|
/* if timeout (no packet received) */
|
|
if(get_timer() <= 0) {
|
|
/* the server doesn't seem to retry let's help out a bit */
|
|
if (tftp_err->no_packets > 4 && port_number != -1
|
|
&& block > 1) {
|
|
send_ack(fn_ip->fd, block, port_number);
|
|
}
|
|
else if (port_number == -1 && block == 0
|
|
&& (tftp_err->no_packets&3) == 3) {
|
|
printf("\nRepeating TFTP read request...\n");
|
|
send_rrq(fn_ip->fd);
|
|
}
|
|
tftp_err->no_packets++;
|
|
set_timer(TICKS_SEC);
|
|
}
|
|
|
|
/* handle received packets */
|
|
receive_ether(fn_ip->fd);
|
|
|
|
/* bad_tftp_packets are counted whenever we receive a TFTP packet
|
|
* which was not expected; if this gets larger than 'retries'
|
|
* we just exit */
|
|
if (tftp_err->bad_tftp_packets > retries) {
|
|
tftp_errno = -40;
|
|
break;
|
|
}
|
|
|
|
/* no_packets counts the times we have returned from receive_ether()
|
|
* without any packet received; if this gets larger than 'retries'
|
|
* we also just exit */
|
|
if (tftp_err->no_packets > retries) {
|
|
tftp_errno = -41;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Setting buffer to NULL disables handling of received TFTP packets.
|
|
buffer = NULL;
|
|
|
|
if (tftp_errno)
|
|
return tftp_errno;
|
|
|
|
print_progress(-1, received_len);
|
|
printf("\n");
|
|
if (lost_packets)
|
|
printf("Lost ACK packets: %d\n", lost_packets);
|
|
|
|
return received_len;
|
|
}
|