From ff8223abf286bfdb5bedc55609cb4cba179a9894 Mon Sep 17 00:00:00 2001 From: jbjohnso Date: Wed, 14 May 2008 15:20:14 +0000 Subject: [PATCH] -Fixes for bad behavior/crashes when multicast request packets might be seen --- atftp/atftp.spec | 5 +- atftp/dfsg-3-to-multicast.diff | 574 +++++++++++++++++++++++++++++++++ 2 files changed, 577 insertions(+), 2 deletions(-) create mode 100644 atftp/dfsg-3-to-multicast.diff diff --git a/atftp/atftp.spec b/atftp/atftp.spec index fa5aadd..0af410a 100644 --- a/atftp/atftp.spec +++ b/atftp/atftp.spec @@ -2,15 +2,15 @@ Name: atftp Summary: Advanced Trivial File Transfer Protocol (ATFTP) - TFTP server Group: System Environment/Daemons Version: 0.7 -Release: 3 +Release: 4 License: GPL Vendor: Linux Networx Inc. Source: atftp_0.7.dfsg.orig.tar.gz Source1: tftpd Patch: atftp_0.7.dfsg-3.diff +Patch1: dfsg-3-to-multicast.diff Buildroot: /var/tmp/atftp-buildroot Packager: Allen Reese -#Provides: tftp-server Conflicts: tftp-server @@ -35,6 +35,7 @@ files using the TFTP protocol. %prep %setup -n atftp-0.7.dfsg %patch -p1 +%patch1 -p1 %build diff --git a/atftp/dfsg-3-to-multicast.diff b/atftp/dfsg-3-to-multicast.diff new file mode 100644 index 0000000..cbf46ce --- /dev/null +++ b/atftp/dfsg-3-to-multicast.diff @@ -0,0 +1,574 @@ +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/options.c atftp-0.7.dev/options.c +--- atftp-0.7.dfsg/options.c 2003-04-24 19:16:18.000000000 -0500 ++++ atftp-0.7.dev/options.c 2008-05-06 11:29:24.000000000 -0500 +@@ -354,3 +354,8 @@ + else + string[0] = 0; + } ++int opt_equal(struct tftp_opt *opt1, struct tftp_opt *opt2) ++{ ++ return ( (strncmp(opt1->option,opt2->option,OPT_SIZE) == 0) && ++ (strncmp(opt1->value,opt2->value,OPT_SIZE) == 0)) ; ++} +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/options.h atftp-0.7.dev/options.h +--- atftp-0.7.dfsg/options.h 2001-07-06 18:35:18.000000000 -0500 ++++ atftp-0.7.dev/options.h 2008-05-06 11:29:24.000000000 -0500 +@@ -45,6 +45,6 @@ + void opt_set_multicast(struct tftp_opt *options, char *addr, int port, int mc); + void opt_request_to_string(struct tftp_opt *options, char *string, int len); + void opt_options_to_string(struct tftp_opt *options, char *string, int len); +- ++int opt_equal(struct tftp_opt *a,struct tftp_opt *b); + #endif + +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftp.c atftp-0.7.dev/tftp.c +--- atftp-0.7.dfsg/tftp.c 2008-05-13 13:45:41.000000000 -0500 ++++ atftp-0.7.dev/tftp.c 2008-05-06 11:29:24.000000000 -0500 +@@ -408,8 +408,7 @@ + */ + int set_peer(int argc, char **argv) + { +- struct hostent *host; /* for host name lookup */ +- struct servent *sp; /* server entry for tftp service */ ++ int port=-1; + + /* sanity check */ + if ((argc < 2) || (argc > 3)) +@@ -418,13 +417,33 @@ + return ERR; + } + +- /* get the server entry */ +- sp = getservbyname("tftp", "udp"); +- if (sp == 0) { +- fprintf(stderr, "tftp: udp/tftp, unknown service.\n"); +- return ERR; ++ /* get the server port */ ++ if (argc == 3) ++ { ++ port = htons(atoi(argv[2])); ++ if (port < 0) ++ { ++ fprintf(stderr, "%s: bad port number.\n", argv[2]); ++ data.connected = 0; ++ return ERR; ++ } ++ data.sa_peer.sin_port = port; ++ } ++ else ++ { ++ /* get the server entry */ ++ struct servent *sp; ++ sp = getservbyname("tftp", "udp"); ++ if (sp == 0) { ++ fprintf(stderr, "tftp: udp/tftp, unknown service.\n"); ++ return ERR; ++ } else ++ { ++ port = sp->s_port; ++ } + } + ++ struct hostent *host; /* for host name lookup */ + /* look up the host */ + host = gethostbyname(argv[1]); + /* if valid, update s_inn structure */ +@@ -437,7 +456,7 @@ + Strncpy(data.hostname, host->h_name, + sizeof(data.hostname)); + data.hostname[sizeof(data.hostname)-1] = 0; +- data.sa_peer.sin_port = sp->s_port; ++ data.sa_peer.sin_port = port; + } + else + { +@@ -445,22 +464,10 @@ + data.connected = 0; + return ERR; + } +- /* get the server port */ +- if (argc == 3) +- { +- sp->s_port = htons(atoi(argv[2])); +- if (sp->s_port < 0) +- { +- fprintf(stderr, "%s: bad port number.\n", argv[2]); +- data.connected = 0; +- return ERR; +- } +- data.sa_peer.sin_port = sp->s_port; +- } + /* copy port number to data structure */ +- data.port = ntohs(sp->s_port); +- ++ data.port = ntohs(port); + data.connected = 1; ++ + return OK; + } + +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftpd.c atftp-0.7.dev/tftpd.c +--- atftp-0.7.dfsg/tftpd.c 2008-05-13 13:45:41.000000000 -0500 ++++ atftp-0.7.dev/tftpd.c 2008-05-06 11:38:38.000000000 -0500 +@@ -197,6 +197,7 @@ + */ + open_logger("atftpd", log_file, logging_level); + logger(LOG_NOTICE, "Advanced Trivial FTP server started (%s)", VERSION); ++ logger(LOG_NOTICE, "Build date: %s %s ", __DATE__,__TIME__); + + #ifdef HAVE_PCRE + /* */ +@@ -474,7 +475,9 @@ + exit(1); + } + new->client_info->done = 0; ++ new->client_info->bytes_sent = 0; + new->client_info->next = NULL; ++ new->client_info->last_ack = -1; + + /* Start a new server thread. */ + if (pthread_create(&tid, NULL, tftpd_receive_request, +@@ -650,8 +653,11 @@ + logger(LOG_ERR, "connect: %s", strerror(errno)); + retval = ABORT; + } +- logger(LOG_DEBUG, "Creating new socket: %s:%d", +- inet_ntoa(to.sin_addr), ntohs(to.sin_port)); ++ logger(LOG_DEBUG, "Creating new socket: %s:%d, on interface 0x%x", ++ inet_ntoa(to.sin_addr), ntohs(to.sin_port),ntohl(to.sin_addr.s_addr)); ++ ++ /* save the dest ip address to bind multicast to correct interface */ ++ data->mcastaddr.imr_interface.s_addr = to.sin_addr.s_addr; + + /* read options from request */ + opt_parse_request(data->data_buffer, data_size, +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftp_def.c atftp-0.7.dev/tftp_def.c +--- atftp-0.7.dfsg/tftp_def.c 2004-02-12 21:16:09.000000000 -0600 ++++ atftp-0.7.dev/tftp_def.c 2008-05-06 11:29:24.000000000 -0500 +@@ -140,8 +140,12 @@ + */ + inline char *Strncpy(char *to, const char *from, size_t size) + { +- to[size-1] = '\000'; +- return strncpy(to, from, size - 1); ++ if (size > 0) ++ { ++ to[size-1] = '\000'; ++ return strncpy(to, from, size - 1); ++ } else ++ return to; + } + + +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftpd_file.c atftp-0.7.dev/tftpd_file.c +--- atftp-0.7.dfsg/tftpd_file.c 2004-02-17 20:21:47.000000000 -0600 ++++ atftp-0.7.dev/tftpd_file.c 2008-05-06 11:29:24.000000000 -0500 +@@ -89,6 +89,27 @@ + return OK; + } + ++int opt_same_file(struct tftp_opt *opt1, struct tftp_opt *opt2) ++{ ++ if ( (strncmp(opt1->option,"filename",OPT_SIZE) == 0) && ++ (strncmp(opt2->option,"filename",OPT_SIZE) == 0)) ++ { ++ char tofilename[MAXLEN]; ++ char fromfilename[MAXLEN]; ++ Strncpy(tofilename, opt1->value, MAXLEN); ++ tftpd_rules_check(tofilename); ++ Strncpy(fromfilename, opt2->value, MAXLEN); ++ tftpd_rules_check(fromfilename); ++ struct stat tostat; ++ struct stat fromstat; ++ if( stat(tofilename,&tostat) || stat(fromfilename,&fromstat)) ++ return 0; ++ ++ return (tostat.st_ino == fromstat.st_ino); ++ } ++ return 0; ++} ++ + /* + * Receive a file. It is implemented as a state machine using a while loop + * and a switch statement. Function flow is as follow: +@@ -117,7 +138,6 @@ + char filename[MAXLEN]; + char string[MAXLEN]; + int timeout = data->timeout; +- int number_of_timeout = 0; + int all_blocks_received = 0; /* temporary kludge */ + int convert = 0; /* if true, do netascii convertion */ + +@@ -265,8 +285,8 @@ + switch (result) + { + case GET_TIMEOUT: +- number_of_timeout++; +- if (number_of_timeout > NB_OF_RETRY) ++ data->client_info->number_of_timeout++; ++ if (data->client_info->number_of_timeout > NB_OF_RETRY) + { + logger(LOG_INFO, "client (%s) not responding", + inet_ntoa(data->client_info->client.sin_addr)); +@@ -322,7 +342,7 @@ + else + logger(LOG_WARNING, "source port mismatch, check bypassed"); + } +- number_of_timeout = 0; ++ data->client_info->number_of_timeout = 0; + state = S_DATA_RECEIVED; + break; + case GET_DISCARD: +@@ -413,13 +433,13 @@ + char filename[MAXLEN]; + char string[MAXLEN]; + int timeout = data->timeout; +- int number_of_timeout = 0; + int mcast_switch = data->mcast_switch_client; + struct stat file_stat; + int convert = 0; /* if true, do netascii conversion */ + struct thread_data *thread = NULL; /* used when looking for a multicast + thread */ + int multicast = 0; /* set to 1 if multicast */ ++ time_t last_send_time=-1; + + struct client_info *client_info = data->client_info; + struct client_info *client_old = NULL; +@@ -428,6 +448,8 @@ + int prev_block_number = 0; /* needed to support netascii convertion */ + int prev_file_pos = 0; + int temp = 0; ++ int total_bytes_sent=0; ++ int clients_served=0; + + /* look for mode option */ + if (strcasecmp(data->tftp_options[OPT_MODE].value, "netascii") == 0) +@@ -439,6 +461,7 @@ + /* file name verification */ + Strncpy(filename, data->tftp_options[OPT_FILENAME].value, + MAXLEN); ++ + if (tftpd_rules_check(filename) != OK) + { + tftp_send_error(sockfd, sa, EACCESS, data->data_buffer, data->data_buffer_size); +@@ -497,7 +520,7 @@ + + /* To return the size of the file with tsize argument */ + fstat(fileno(fp), &file_stat); +- ++ + /* tsize option */ + if ((opt_get_tsize(data->tftp_options) > -1) && !convert) + { +@@ -535,6 +558,32 @@ + return ERR; + } + ++ ++ /* make sure that the oack packet will fit in the buffer */ ++ int oacklen = 2; ++ int i; ++ for (i = 2; i < OPT_NUMBER; i++) ++ { ++ if (data->tftp_options[i].enabled && data->tftp_options[i].specified) ++ { ++ oacklen += strlen(data->tftp_options[i].option); ++ oacklen++; ++ oacklen += strlen(data->tftp_options[i].value); ++ oacklen++; ++ } ++ } ++ ++ if (oacklen > result) ++ { ++ logger(LOG_NOTICE, "OACK will not fit in buffer of size %d. Options rejected.", result); ++ tftp_send_error(sockfd, sa, EOPTNEG, data->data_buffer, data->data_buffer_size); ++ if (data->trace) ++ logger(LOG_DEBUG, "sent ERROR ", EOPTNEG, ++ tftp_errmsg[EOPTNEG]); ++ fclose(fp); ++ return ERR; ++ } ++ + data->data_buffer_size = result + 4; + data->data_buffer = realloc(data->data_buffer, data->data_buffer_size); + +@@ -649,9 +698,14 @@ + /* initialise multicast address structure */ + data->mcastaddr.imr_multiaddr.s_addr = + data->sa_mcast.sin_addr.s_addr; +- data->mcastaddr.imr_interface.s_addr = htonl(INADDR_ANY); ++ + setsockopt(data->sockfd, IPPROTO_IP, IP_MULTICAST_TTL, + &data->mcast_ttl, sizeof(data->mcast_ttl)); ++ ++ logger(LOG_DEBUG, "Multicast interface = %s \n",inet_ntoa(data->mcastaddr.imr_interface)); ++ setsockopt(data->sockfd, IPPROTO_IP, IP_MULTICAST_IF, ++ &(data->mcastaddr.imr_interface.s_addr), ++ sizeof(data->mcastaddr.imr_interface.s_addr)); + + /* set options data for OACK */ + opt_set_multicast(data->tftp_options, data->mc_addr, +@@ -674,6 +728,7 @@ + memcpy(options, data->tftp_options, sizeof(options)); + opt_set_multicast(options, data->mc_addr, data->mc_port, 0); + ++ + /* That's it, ready to send the file */ + while (1) + { +@@ -728,6 +783,7 @@ + tftp_send_data(sockfd, &data->sa_mcast, + block_number + 1, data_size, + data->data_buffer); ++ client_info->bytes_sent += data_size-4; + } + else + { +@@ -737,6 +793,8 @@ + if (data->trace) + logger(LOG_DEBUG, "sent DATA ", + block_number + 1, data_size - 4); ++ time(&last_send_time); ++ + state = S_WAIT_PACKET; + break; + case S_WAIT_PACKET: +@@ -746,12 +804,12 @@ + switch (result) + { + case GET_TIMEOUT: +- number_of_timeout++; ++ client_info->number_of_timeout++; + +- if (number_of_timeout > NB_OF_RETRY) ++ if (client_info->number_of_timeout > NB_OF_RETRY) + { +- logger(LOG_INFO, "client (%s) not responding", +- inet_ntoa(client_info->client.sin_addr)); ++ logger(LOG_INFO, "client (%s) not responding. state=%d block_number=%d", ++ inet_ntoa(client_info->client.sin_addr),timeout_state,block_number); + state = S_END; + } + else +@@ -779,7 +837,8 @@ + /* Proceed normally with the next client, + going to OACK state */ + logger(LOG_INFO, +- "Serving next client: %s:%d", ++ "Serving next client after timeout: state=%d, block_number=%d: %s:%d", ++ timeout_state,block_number, + inet_ntoa(client_info->client.sin_addr), + ntohs(client_info->client.sin_port)); + sa = &client_info->client; +@@ -796,7 +855,7 @@ + break; + } + } +- logger(LOG_WARNING, "timeout: retrying..."); ++ logger(LOG_WARNING, "timeout: retrying... state=%d, block_number=%d",timeout_state,block_number); + state = timeout_state; + } + break; +@@ -811,7 +870,13 @@ + * If this is an ACK for the last block, mark this client as + * done + */ +- if ((last_block != -1) && (block_number > last_block)) ++ logger(LOG_DEBUG, ++ "received ACK from wrong client: %s:%d", ++ ntohs(tftphdr->th_block), ++ inet_ntoa(from.sin_addr), ++ ntohs(from.sin_port)); ++ ++ if ((last_block != -1) && (ntohs(tftphdr->th_block) > last_block)) + { + if (tftpd_clientlist_done(data, NULL, &from) == 1) + logger(LOG_DEBUG, "client done <%s>", +@@ -851,11 +916,30 @@ + } + } + /* The ACK is from the current client */ +- number_of_timeout = 0; +- block_number = ntohs(tftphdr->th_block); ++ client_info->number_of_timeout = 0; ++ int ACK_block_number = ntohs(tftphdr->th_block); ++ if (ACK_block_number == client_info->last_ack) { ++ /* duplicate ACK, ignore */ ++ time_t now; ++ time(&now); ++ /* if a timeout has occurred, resend last block */ ++ if ((now-last_send_time) > timeout) { ++ state = S_SEND_DATA; ++ logger(LOG_DEBUG, "Duplicate ACK packet discarded <%d>, timeout. Resend last block.", ACK_block_number); ++ } else { ++ logger(LOG_DEBUG, "Duplicate ACK packet discarded <%d>.", ACK_block_number); ++ } ++ break; ++ } ++ ++ client_info->last_ack = ACK_block_number; ++ ++ block_number = ACK_block_number; ++ + if (data->trace) + logger(LOG_DEBUG, "received ACK ", + block_number); ++ + if ((last_block != -1) && (block_number > last_block)) + { + state = S_END; +@@ -932,10 +1016,14 @@ + } + break; + case S_END: ++ total_bytes_sent+=client_info->bytes_sent; + if (multicast) + { + logger(LOG_DEBUG, "End of multicast transfer"); ++ logger(LOG_INFO, ++ "Bytes sent while this client was master: %d",client_info->bytes_sent); + /* mark the current client done */ ++ clients_served++; + tftpd_clientlist_done(data, client_info, NULL); + /* Look if there is another client to serve. We lock list of + client to make sure no other thread try to add clients in +@@ -948,13 +1036,21 @@ + ntohs(client_info->client.sin_port)); + /* client is a new client structure */ + sa = &client_info->client; +- /* nedd to send an oack to that client */ ++ ++ /* send an oack to that client */ + state = S_SEND_OACK; + fseek(fp, 0, SEEK_SET); + } + else + { +- logger(LOG_INFO, "No more client, end of tranfers"); ++ int fs = file_stat.st_size; ++ int blksze = (data->data_buffer_size - 4); ++ int ttlblks = fs/blksze; ++ int blksretry = (total_bytes_sent-file_stat.st_size)/blksze; ++ logger(LOG_INFO, "No more client, end of tranfers. %d clients served",clients_served); ++ logger(LOG_INFO, "Bytes saved over unicast: %ld", (clients_served*file_stat.st_size)-total_bytes_sent); ++ logger(LOG_INFO, "File size: %d, total data bytes sent %d",file_stat.st_size,total_bytes_sent); ++ logger(LOG_INFO, "Block re-sent: %d of %d = %f percent",blksretry, ttlblks, ((float)blksretry/(float)ttlblks)*100); + fclose(fp); + return OK; + } +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftpd.h atftp-0.7.dev/tftpd.h +--- atftp-0.7.dfsg/tftpd.h 2004-02-26 20:05:26.000000000 -0600 ++++ atftp-0.7.dev/tftpd.h 2008-05-06 11:29:24.000000000 -0500 +@@ -71,6 +71,9 @@ + struct client_info { + struct sockaddr_in client; + int done; /* that client as receive it's file */ ++ int bytes_sent; ++ int number_of_timeout; /* number of timeouts while sending to this client */ ++ int last_ack; /* last ACK received from this client */ + struct client_info *next; + }; + +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftpd_list.c atftp-0.7.dev/tftpd_list.c +--- atftp-0.7.dfsg/tftpd_list.c 2004-02-26 20:05:26.000000000 -0600 ++++ atftp-0.7.dev/tftpd_list.c 2008-05-06 11:29:24.000000000 -0500 +@@ -140,17 +140,9 @@ + struct thread_data *current = thread_data; /* head of the list */ + struct tftp_opt *tftp_options = data->tftp_options; + struct client_info *tmp; +- char options[MAXLEN]; +- char string[MAXLEN]; +- char *index; +- int len; + + *thread = NULL; + +- opt_request_to_string(tftp_options, options, MAXLEN); +- index = strstr(options, "multicast"); +- len = (int)index - (int)options; +- + /* lock the whole list before walking it */ + pthread_mutex_lock(&thread_list_mutex); + +@@ -162,9 +154,9 @@ + pthread_mutex_lock(¤t->client_mutex); + if (current->client_ready == 1) + { +- opt_request_to_string(current->tftp_options, string, MAXLEN); +- /* must have exact same option string */ +- if (strncmp(string, options, len) == 0) ++ /* must have exact same mode and refer to the same file */ ++ if (opt_same_file(current->tftp_options,tftp_options) && ++ opt_equal(&(current->tftp_options[OPT_MODE]), &(tftp_options[OPT_MODE]))) + { + *thread = current; + /* insert the new client at the end. If the client is already +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftpd_mtftp.c atftp-0.7.dev/tftpd_mtftp.c +--- atftp-0.7.dfsg/tftpd_mtftp.c 2004-02-26 20:05:26.000000000 -0600 ++++ atftp-0.7.dev/tftpd_mtftp.c 2008-05-06 11:29:24.000000000 -0500 +@@ -369,6 +369,13 @@ + logger(LOG_ERR, "mtftp: can't open socket"); + pthread_exit(NULL); + } ++ ++ int one=1; ++ if (setsockopt(sockfd, SOL_IP, IP_PKTINFO, &one, sizeof(one)) != 0) ++ { ++ logger(LOG_WARNING, "Failed to set socket option: %s", strerror(errno)); ++ } ++ + /* bind the socket to the tftp port */ + if (bind(sockfd, (struct sockaddr*)&sa, sizeof(sa)) < 0) + { +@@ -389,7 +396,8 @@ + that file name */ + memset(&sa, 0, sizeof(sa)); /* this will hold the client info */ + data_size = data->data_buffer_size; +- retval = tftp_get_packet(sockfd, -1, NULL, &sa, NULL, NULL, ++ struct sockaddr_in toaddr; ++ retval = tftp_get_packet(sockfd, -1, NULL, &sa, NULL, &toaddr, + data->timeout, + &data_size, data->data_buffer); + +@@ -463,6 +471,7 @@ + getsockname(sockfd, (struct sockaddr *)&(sa), &len); + //memset(&sa, 0, sizeof(sa)); + sa.sin_port = 0; ++ + /* bind the socket to the tftp port */ + if (bind(thread->sockfd, (struct sockaddr*)&sa, sizeof(sa)) < 0) + { +@@ -472,8 +481,12 @@ + getsockname(thread->sockfd, (struct sockaddr *)&(sa), &len); + + /* configure multicast socket */ +- thread->mcastaddr.imr_multiaddr.s_addr = thread->sa_mcast.sin_addr.s_addr; +- thread->mcastaddr.imr_interface.s_addr = htonl(INADDR_ANY); ++ thread->mcastaddr.imr_interface.s_addr = toaddr.sin_addr.s_addr; ++ ++ setsockopt(thread->sockfd, IPPROTO_IP, IP_MULTICAST_IF, ++ &(thread->mcastaddr.imr_interface.s_addr), ++ sizeof(thread->mcastaddr.imr_interface.s_addr)); ++ + setsockopt(thread->sockfd, IPPROTO_IP, IP_MULTICAST_TTL, + &data->mcast_ttl, sizeof(data->mcast_ttl)); + +diff -uNr --exclude='*[^ch]' atftp-0.7.dfsg/tftp_file.c atftp-0.7.dev/tftp_file.c +--- atftp-0.7.dfsg/tftp_file.c 2008-05-13 13:45:41.000000000 -0500 ++++ atftp-0.7.dev/tftp_file.c 2008-05-06 11:29:24.000000000 -0500 +@@ -484,6 +484,14 @@ + sa_mcast.sin_family = AF_INET; + sa_mcast.sin_addr.s_addr = htonl(INADDR_ANY); + sa_mcast.sin_port = htons(mc_port); ++ int yes=1; ++ if (setsockopt(mcast_sockfd, SOL_SOCKET, ++ SO_REUSEADDR, ++ &yes, sizeof(yes)) < 0) ++ { ++ perror("setsockopt"); ++ exit(1); ++ } + + if (bind(mcast_sockfd, (struct sockaddr *)&sa_mcast, + sizeof(sa_mcast)) < 0)