text: /******************************************************************************
   * 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>
  #include <dns.h>
  
  //#define __DEBUG__
  
  #define MAX_BLOCKSIZE 1428
  #define BUFFER_LEN 256
  #define INVALID_BUFFER ((void *)-1L)
  
  #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 = INVALID_BUFFER;
  static unsigned short block;
  static unsigned short blocksize;
  static char blocksize_str[6];    /* Blocksize string for read request */
  static int received_len;
  static unsigned int retries;
  static int huge_load;
  static int len;
  static int tftp_finished;
  static int lost_packets;
  static int tftp_errno;
  static int ip_version;
  static short port_number;
  static tftp_err_t *tftp_err;
  static filename_ip_t  *fn_ip;
  static int progress_first;
  static int progress_last_bytes;
  
  /**
   * 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;
  	char buffer[100];
  	char *ptr;
  
  	// 1MB steps or 0x400 times or urgent
  	if(((received_bytes - progress_last_bytes) >> 20) > 0
  	|| (i & 0x3FF) == 0 || urgent) {
  		if (!progress_first) {
  			sprintf(buffer, "%d KBytes", (progress_last_bytes >> 10));
  			for(ptr = buffer; *ptr != 0; ++ptr)
  				*ptr = '\b';
  			printf("%s", buffer);
  		}
  		printf("%d KBytes", (received_bytes >> 10));
  		i = 1;
  		progress_first = 0;
  		progress_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 == INVALID_BUFFER)
  		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;
  
  	block = 0;
  	received_len = 0;
  	tftp_finished = 0;
  	lost_packets = 0;
  	port_number = -1;
  	progress_first = -1;
  	progress_last_bytes = 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);
  
  	/* Set buffer to a valid address, enables handling of received 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 invalid to disable handling of received packets */
  	buffer = INVALID_BUFFER;
  
  	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;
  }
  
  /**
   * Parses a tftp arguments, extracts all
   * parameters and fills server ip according to this
   *
   * Parameters:
   * @param  buffer        string with arguments,
   * @param  server_ip	 server ip as result
   * @param  filename	 default filename
   * @param  fd            Socket descriptor
   * @param  len           len of the buffer,
   * @return               0 on SUCCESS and -1 on failure
   */
  int parse_tftp_args(char buffer[], char *server_ip, char filename[], int fd,
  		    int len)
  {
  	char *raw;
  	char *tmp, *tmp1;
  	int i, j = 0;
  	char domainname[256];
  	uint8_t server_ip6[16];
  
  	raw = malloc(len);
  	if (raw == NULL) {
  		printf("\n unable to allocate memory, parsing failed\n");
  		return -1;
  	}
  	strncpy(raw, (const char *)buffer, len);
  	/* tftp url contains tftp://[fd00:4f53:4444:90:214:5eff:fed9:b200]/testfile */
  	if (strncmp(raw, "tftp://", 7)){
  		printf("\n tftp missing in %s\n", raw);
  		free(raw);
  		return -1;
  	}
  	tmp = strchr(raw, '[');
  	if (tmp != NULL && *tmp == '[') {
  		/* check for valid ipv6 address */
  		tmp1 = strchr(tmp, ']');
  		if (tmp1 == NULL) {
  			printf("\n missing ] in %s\n", raw);
  			free(raw);
  			return -1;
  		}
  		i = tmp1 - tmp;
  		/* look for file name */
  		tmp1 = strchr(tmp, '/');
  		if (tmp1 == NULL) {
  			printf("\n missing filename in %s\n", raw);
  			free(raw);
  			return -1;
  		}
  		tmp[i] = '\0';
  		/* check for 16 byte ipv6 address */
  		if (!str_to_ipv6(tmp + 1, (uint8_t *)server_ip)) {
  			printf("\n wrong format IPV6 address in %s\n", raw);
  			free(raw);
  			return -1;;
  		}
  		else {
  			/* found filename */
  			strcpy(filename, tmp1 + 1);
  			free(raw);
  			return 0;
  		}
  	}
  	else {
  		/* here tftp://hostname/testfile from option request of dhcp */
  		/* look for dns server name */
  		tmp1 = strchr(raw, '.');
  		if (tmp1 == NULL) {
  			printf("\n missing . seperator in %s\n", raw);
  			free(raw);
  			return -1;
  		}
  		/* look for domain name beyond dns server name
  		 * so ignore the current . and look for one more */
  		tmp = strchr(tmp1 + 1, '.');
  		if (tmp == NULL) {
  			printf("\n missing domain in %s\n", raw);
  			free(raw);
  			return -1;
  		}
  		tmp1 = strchr(tmp1, '/');
  		if (tmp1 == NULL) {
  			printf("\n missing filename in %s\n", raw);
  			free(raw);
  			return -1;
  		}
  		j = tmp1 - (raw + 7);
  		tmp = raw + 7;
  		tmp[j] = '\0';
  		strcpy(domainname, tmp);
  		if (dns_get_ip(fd, domainname, server_ip6, 6) == 0) {
  			printf("\n DNS failed for IPV6\n");
  			return -1;
  		}
  		ipv6_to_str(server_ip6, server_ip);
  
  		strcpy(filename, tmp1 + 1);
  		free(raw);
  		return 0;
  	}
  }
  