END_TEST START_TEST(tc_tftp_send_data) { example_session.state = 0; example_session.socket = &example_socket; called_sendto = 0; expected_opcode = PICO_TFTP_DATA; tftp_send_data(&example_session, (const uint8_t*)"buffer", strlen("buffer")); fail_if(called_sendto < 1); fail_if(example_session.state != PICO_TFTP_STATE_TX_LAST); }
static void tftp_handle_ack(PNATState pData, struct tftp_t *tp, int pktlen) { int s; s = tftp_session_find(pData, tp); if (s < 0) return; if (tftp_send_data(pData, &tftp_sessions[s], RT_N2H_U16(tp->x.tp_data.tp_block_nr) + 1, tp) < 0) { /* XXX */ } }
static void tftp_handle_ack(Slirp *slirp, struct tftp_t *tp, int pktlen) { int s; s = tftp_session_find(slirp, tp); if (s < 0) { return; } if (tftp_send_data(&slirp->tftp_sessions[s], ntohs(tp->x.tp_data.tp_block_nr) + 1, tp) < 0) { return; } }
/* * Send a file. It is implemented as a state machine using a while loop * and a switch statement. Function flow is as follow: * - sanity check * - check client's request * - enter state machine * * 1) send a DATA or OACK * 2) wait replay * - if ACK, goto 3 * - if ERROR abort * - if TIMEOUT goto previous state * 3) send data, goto 2 */ int tftpd_send_file(struct thread_data *data) { int state = S_BEGIN; int timeout_state = state; int result; int block_number = 0; int last_block = -1; int data_size; struct sockaddr_storage *sa = &data->client_info->client; struct sockaddr_storage from; char addr_str[SOCKADDR_PRINT_ADDR_LEN]; int sockfd = data->sockfd; struct tftphdr *tftphdr = (struct tftphdr *)data->data_buffer; FILE *fp; 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 */ struct client_info *client_info = data->client_info; struct client_info *client_old = NULL; struct tftp_opt options[OPT_NUMBER]; int prev_block_number = 0; /* needed to support netascii convertion */ int prev_file_pos = 0; int temp = 0; /* look for mode option */ if (strcasecmp(data->tftp_options[OPT_MODE].value, "netascii") == 0) { convert = 1; logger(LOG_DEBUG, "will do netascii convertion"); } /* 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); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EACCESS, tftp_errmsg[EACCESS]); return ERR; } /* verify that the requested file exist */ fp = fopen(filename, "r"); #ifdef HAVE_PCRE if (fp == NULL) { /* Verify if this file have a working subsitution */ if (pcre_top != NULL) { if (tftpd_pcre_sub(pcre_top, string, MAXLEN, data->tftp_options[OPT_FILENAME].value) < 0) { logger(LOG_DEBUG, "PCRE failed to match"); } else { logger(LOG_INFO, "PCRE mapped %s -> %s", data->tftp_options[OPT_FILENAME].value, string); Strncpy(filename, string, MAXLEN); /* recheck those rules */ if (tftpd_rules_check(filename) != OK) { tftp_send_error(sockfd, sa, EACCESS, data->data_buffer, data->data_buffer_size); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EACCESS, tftp_errmsg[EACCESS]); return ERR; } /* write back the new file name to the option structure */ opt_set_options(data->tftp_options, "filename", filename); /* try to open this new file */ fp = fopen(filename, "r"); } } } #endif if (fp == NULL) { tftp_send_error(sockfd, sa, ENOTFOUND, data->data_buffer, data->data_buffer_size); logger(LOG_INFO, "File %s not found", filename); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", ENOTFOUND, tftp_errmsg[ENOTFOUND]); return ERR; } /* 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) { opt_set_tsize(file_stat.st_size, data->tftp_options); logger(LOG_INFO, "tsize option -> %d", file_stat.st_size); } /* timeout option */ if ((result = opt_get_timeout(data->tftp_options)) > -1) { if ((result < 1) || (result > 255)) { tftp_send_error(sockfd, sa, EOPTNEG, data->data_buffer, data->data_buffer_size); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EOPTNEG, tftp_errmsg[EOPTNEG]); fclose(fp); return ERR; } timeout = result; opt_set_timeout(timeout, data->tftp_options); logger(LOG_INFO, "timeout option -> %d", timeout); } /* blksize options */ if ((result = opt_get_blksize(data->tftp_options)) > -1) { if ((result < 8) || (result > 65464)) { tftp_send_error(sockfd, sa, EOPTNEG, data->data_buffer, data->data_buffer_size); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", 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); if (data->data_buffer == NULL) { logger(LOG_ERR, "memory allocation failure"); fclose(fp); return ERR; } tftphdr = (struct tftphdr *)data->data_buffer; if (data->data_buffer == NULL) { tftp_send_error(sockfd, sa, ENOSPACE, data->data_buffer, data->data_buffer_size); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", ENOSPACE, tftp_errmsg[ENOSPACE]); fclose(fp); return ERR; } opt_set_blksize(result, data->tftp_options); logger(LOG_INFO, "blksize option -> %d", result); } /* Verify that the file can be sent in 2^16 block of BLKSIZE octets */ if ((file_stat.st_size / (data->data_buffer_size - 4)) > 65535) { tftp_send_error(sockfd, sa, EUNDEF, data->data_buffer, data->data_buffer_size); logger(LOG_NOTICE, "Requested file to big, increase BLKSIZE"); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EUNDEF, tftp_errmsg[EUNDEF]); fclose(fp); return ERR; } /* multicast option */ if (data->tftp_options[OPT_MULTICAST].specified && data->tftp_options[OPT_MULTICAST].enabled && !convert) { /* * Find a server with the same options to give up the client. */ logger(LOG_DEBUG, "Searching a server thread to give up this client"); result = tftpd_list_find_multicast_server_and_add(&thread, data, data->client_info); if ( result > 0) { /* add this client to its list of client */ if (result == 1) logger(LOG_DEBUG, "Added client %p to thread %p", data->client_info, thread); else logger(LOG_DEBUG, "Client (%p) is already in list of thread %p", data->client_info, thread); /* NULL our own pointer so we don't free memory */ if (result == 1) data->client_info = NULL; /* Look at needed information to oack that client */ opt_set_multicast(data->tftp_options, thread->mc_addr, thread->mc_port, 0); logger(LOG_INFO, "multicast option -> %s,%d,%d", thread->mc_addr, thread->mc_port, 0); /* Send an OACK to that client. There is a possible race condition here where the new server thread OACK this client before us. This should not be a problem: the client thread will receive a second OACK and fall back to non master mode. Then the server will timeout and either resend OACK or continu with the next client */ opt_options_to_string(data->tftp_options, string, MAXLEN); if (data->trace) logger(LOG_DEBUG, "sent OACK <%s>", string); tftp_send_oack(thread->sockfd, sa, data->tftp_options, data->data_buffer, data->data_buffer_size); /* We are done */ logger(LOG_INFO, "Client transfered to %p", thread); fclose(fp); return OK; } else { struct addrinfo hints, *result; /* configure socket, get an IP address */ if (tftpd_mcast_get_tid(&data->mc_addr, &data->mc_port) != OK) { logger(LOG_ERR, "No multicast address/port available"); fclose(fp); return ERR; } logger(LOG_DEBUG, "mcast_addr: %s, mcast_port: %d", data->mc_addr, data->mc_port); /* convert address */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(data->mc_addr, NULL, &hints, &result) || sockaddr_set_addrinfo(&data->sa_mcast, result)) { logger(LOG_ERR, "bad address %s\n",data->mc_addr); fclose(fp); return ERR; } freeaddrinfo(result); sockaddr_set_port(&data->sa_mcast, data->mc_port); /* verify address is multicast */ if (!sockaddr_is_multicast(&data->sa_mcast)) { logger(LOG_ERR, "bad multicast address %s\n", sockaddr_print_addr(&data->sa_mcast, addr_str, sizeof(addr_str))); fclose(fp); return ERR; } /* initialise multicast address structure */ sockaddr_get_mreq(&data->sa_mcast, &data->mcastaddr); if (data->sa_mcast.ss_family == AF_INET) setsockopt(data->sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &data->mcast_ttl, sizeof(data->mcast_ttl)); else setsockopt(data->sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &data->mcast_ttl, sizeof(data->mcast_ttl)); /* set options data for OACK */ opt_set_multicast(data->tftp_options, data->mc_addr, data->mc_port, 1); logger(LOG_INFO, "multicast option -> %s,%d,%d", data->mc_addr, data->mc_port, 1); /* the socket must be unconnected for multicast */ sa->ss_family = AF_UNSPEC; connect(sockfd, (struct sockaddr *)sa, sizeof(*sa)); /* set multicast flag */ multicast = 1; /* Now ready to receive new clients */ tftpd_clientlist_ready(data); } } /* copy options to local structure, used when falling back a client to slave */ 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) { if (tftpd_cancel) { /* Send error to all client */ logger(LOG_DEBUG, "thread cancelled"); do { tftpd_clientlist_done(data, client_info, NULL); tftp_send_error(sockfd, &client_info->client, EUNDEF, data->data_buffer, data->data_buffer_size); if (data->trace) { logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s> to %s", EUNDEF, tftp_errmsg[EUNDEF], sockaddr_print_addr(&client_info->client, addr_str, sizeof(addr_str))); } } while (tftpd_clientlist_next(data, &client_info) == 1); state = S_ABORT; } switch (state) { case S_BEGIN: if (opt_support_options(data->tftp_options)) state = S_SEND_OACK; else state = S_SEND_DATA; break; case S_SEND_OACK: timeout_state = state; opt_options_to_string(data->tftp_options, string, MAXLEN); if (data->trace) logger(LOG_DEBUG, "sent OACK <%s>", string); tftp_send_oack(sockfd, sa, data->tftp_options, data->data_buffer, data->data_buffer_size); state = S_WAIT_PACKET; break; case S_SEND_DATA: timeout_state = state; data_size = tftp_file_read(fp, tftphdr->th_data, data->data_buffer_size - 4, block_number, convert, &prev_block_number, &prev_file_pos, &temp); data_size += 4; /* need to consider tftp header */ /* record the last block number */ if (feof(fp)) last_block = block_number; if (multicast) { tftp_send_data(sockfd, &data->sa_mcast, block_number + 1, data_size, data->data_buffer); } else { tftp_send_data(sockfd, sa, block_number + 1, data_size, data->data_buffer); } if (data->trace) logger(LOG_DEBUG, "sent DATA <block: %d, size %d>", block_number + 1, data_size - 4); state = S_WAIT_PACKET; break; case S_WAIT_PACKET: data_size = data->data_buffer_size; result = tftp_get_packet(sockfd, -1, NULL, sa, &from, NULL, timeout, &data_size, data->data_buffer); switch (result) { case GET_TIMEOUT: number_of_timeout++; if (number_of_timeout > NB_OF_RETRY) { logger(LOG_INFO, "client (%s) not responding", sockaddr_print_addr(&client_info->client, addr_str, sizeof(addr_str))); state = S_END; } else { /* The client failed to ACK our last packet. Send an OACK with mc=0 to it, fetch the next client in the list and continu with this new one */ if (multicast && mcast_switch) { client_old = client_info; tftpd_clientlist_next(data, &client_info); if (client_info && (client_info != client_old)) { /* Send an OACK to the old client remove is master client status */ opt_options_to_string(options, string, MAXLEN); if (data->trace) logger(LOG_DEBUG, "sent OACK <%s>", string); tftp_send_oack(sockfd, sa, options, data->data_buffer, data->data_buffer_size); /* Proceed normally with the next client, going to OACK state */ logger(LOG_INFO, "Serving next client: %s:%d", sockaddr_print_addr( &client_info->client, addr_str, sizeof(addr_str)), sockaddr_get_port( &client_info->client)); sa = &client_info->client; state = S_SEND_OACK; break; } else if (client_info == NULL) { /* we got a big problem if this happend */ logger(LOG_ERR, "%s: %d: abnormal condition", __FILE__, __LINE__); state = S_ABORT; break; } } logger(LOG_WARNING, "timeout: retrying..."); state = timeout_state; } break; case GET_ACK: /* handle case where packet come from un unexpected client */ if (multicast) { if (!sockaddr_equal(sa, &from)) { /* We got an ACK from a client that is not the master client. * If this is an ACK for the last block, mark this client as * done */ if ((last_block != -1) && (block_number > last_block)) { if (tftpd_clientlist_done(data, NULL, &from) == 1) logger(LOG_DEBUG, "client done <%s>", sockaddr_print_addr( &from, addr_str, sizeof(addr_str))); else logger(LOG_WARNING, "packet discarded <%s:%d>", sockaddr_print_addr( &from, addr_str, sizeof(addr_str)), sockaddr_get_port(&from)); } else /* If not, send and OACK with mc=0 to shut it up. */ { opt_options_to_string(options, string, MAXLEN); if (data->trace) logger(LOG_DEBUG, "sent OACK <%s>", string); tftp_send_oack(sockfd, &from, options, data->data_buffer, data->data_buffer_size); } break; } } else { /* check that the packet is from the current client */ if (sockaddr_get_port(sa) != sockaddr_get_port(&from)) { if (data->checkport) { logger(LOG_WARNING, "packet discarded <%s:%d>", sockaddr_print_addr(&from, addr_str, sizeof(addr_str)), sockaddr_get_port(&from)); break; } else logger(LOG_WARNING, "source port mismatch, check bypassed"); } } /* The ACK is from the current client */ number_of_timeout = 0; block_number = ntohs(tftphdr->th_block); if (data->trace) logger(LOG_DEBUG, "received ACK <block: %d>", block_number); if ((last_block != -1) && (block_number > last_block)) { state = S_END; break; } state = S_SEND_DATA; break; case GET_ERROR: /* handle case where packet come from un unexpected client */ if (multicast) { /* if packet is not from the current master client */ if (!sockaddr_equal(sa, &from)) { /* mark this client done */ if (tftpd_clientlist_done(data, NULL, &from) == 1) { if (data->trace) logger(LOG_DEBUG, "client sent ERROR, mark as done <%s>", sockaddr_print_addr( &from, addr_str, sizeof(addr_str))); } else logger(LOG_WARNING, "packet discarded <%s>", sockaddr_print_addr(&from, addr_str, sizeof(addr_str))); /* current state is unchanged */ break; } } else { /* check that the packet is from the current client */ if (sockaddr_get_port(sa) != sockaddr_get_port(&from)) { if (data->checkport) { logger(LOG_WARNING, "packet discarded <%s>", sockaddr_print_addr(&from, addr_str, sizeof(addr_str))); break; } else logger(LOG_WARNING, "source port mismatch, check bypassed"); } } /* Got an ERROR from the current master client */ Strncpy(string, tftphdr->th_msg, (((data_size - 4) > MAXLEN) ? MAXLEN : (data_size - 4))); if (data->trace) logger(LOG_DEBUG, "received ERROR <code: %d, msg: %s>", ntohs(tftphdr->th_code), string); if (multicast) { logger(LOG_DEBUG, "Marking client as done"); state = S_END; } else state = S_ABORT; break; case GET_DISCARD: /* FIXME: should we increment number_of_timeout */ logger(LOG_WARNING, "packet discarded <%s>", sockaddr_print_addr(&from, addr_str, sizeof(addr_str))); break; case ERR: logger(LOG_ERR, "%s: %d: recvfrom: %s", __FILE__, __LINE__, strerror(errno)); state = S_ABORT; break; default: logger(LOG_ERR, "%s: %d: abnormal return value %d", __FILE__, __LINE__, result); } break; case S_END: if (multicast) { logger(LOG_DEBUG, "End of multicast transfer"); /* mark the current client done */ 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 our back */ if (tftpd_clientlist_next(data, &client_info) == 1) { logger(LOG_INFO, "Serving next client: %s:%d", sockaddr_print_addr(&client_info->client, addr_str, sizeof(addr_str)), sockaddr_get_port(&client_info->client)); /* client is a new client structure */ sa = &client_info->client; /* nedd to 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"); fclose(fp); return OK; } } else { logger(LOG_DEBUG, "End of transfer"); fclose(fp); return OK; } break; case S_ABORT: logger(LOG_DEBUG, "Aborting transfer"); fclose(fp); return ERR; default: fclose(fp); logger(LOG_ERR, "%s: %d: abnormal condition", __FILE__, __LINE__); return ERR; } } }
static void tftp_handle_rrq(Slirp *slirp, struct tftp_t *tp, int pktlen) { struct tftp_session *spt; int s, k; size_t prefix_len; char *req_fname; s = tftp_session_allocate(slirp, tp); if (s < 0) { return; } spt = &slirp->tftp_sessions[s]; /* unspecifed prefix means service disabled */ if (!slirp->tftp_prefix) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* skip header fields */ k = 0; pktlen -= ((uint8_t *)&tp->x.tp_buf[0] - (uint8_t *)tp); /* prepend tftp_prefix */ prefix_len = strlen(slirp->tftp_prefix); spt->filename = qemu_malloc(prefix_len + TFTP_FILENAME_MAX + 2); memcpy(spt->filename, slirp->tftp_prefix, prefix_len); spt->filename[prefix_len] = '/'; /* get name */ req_fname = spt->filename + prefix_len + 1; while (1) { if (k >= TFTP_FILENAME_MAX || k >= pktlen) { tftp_send_error(spt, 2, "Access violation", tp); return; } req_fname[k] = (char)tp->x.tp_buf[k]; if (req_fname[k++] == '\0') { break; } } /* check mode */ if ((pktlen - k) < 6) { tftp_send_error(spt, 2, "Access violation", tp); return; } if (memcmp(&tp->x.tp_buf[k], "octet\0", 6) != 0) { tftp_send_error(spt, 4, "Unsupported transfer mode", tp); return; } k += 6; /* skipping octet */ /* do sanity checks on the filename */ if (!strncmp(req_fname, "../", 3) || req_fname[strlen(req_fname) - 1] == '/' || strstr(req_fname, "/../")) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* check if the file exists */ if (tftp_read_data(spt, 0, NULL, 0) < 0) { tftp_send_error(spt, 1, "File not found", tp); return; } if (tp->x.tp_buf[pktlen - 1] != 0) { tftp_send_error(spt, 2, "Access violation", tp); return; } while (k < pktlen) { const char *key, *value; key = (const char *)&tp->x.tp_buf[k]; k += strlen(key) + 1; if (k >= pktlen) { tftp_send_error(spt, 2, "Access violation", tp); return; } value = (const char *)&tp->x.tp_buf[k]; k += strlen(value) + 1; if (strcmp(key, "tsize") == 0) { int tsize = atoi(value); struct stat stat_p; if (tsize == 0) { if (stat(spt->filename, &stat_p) == 0) tsize = stat_p.st_size; else { tftp_send_error(spt, 1, "File not found", tp); return; } } tftp_send_oack(spt, "tsize", tsize, tp); } } tftp_send_data(spt, 1, tp); }
static void tftp_handle_rrq(struct tftp_t *tp, int pktlen) { struct tftp_session *spt; int s, k, n; u_int8_t *src, *dst; s = tftp_session_allocate(tp); if (s < 0) { return; } spt = &tftp_sessions[s]; src = tp->x.tp_buf; dst = spt->filename; n = pktlen - ((uint8_t *)&tp->x.tp_buf[0] - (uint8_t *)tp); /* get name */ for (k = 0; k < n; k++) { if (k < TFTP_FILENAME_MAX) { dst[k] = src[k]; } else { return; } if (src[k] == '\0') { break; } } if (k >= n) { return; } k++; /* check mode */ if ((n - k) < 6) { return; } if (memcmp(&src[k], "octet\0", 6) != 0) { tftp_send_error(spt, 4, "Unsupported transfer mode", tp); return; } k += 6; /* skipping octet */ /* do sanity checks on the filename */ if ((spt->filename[0] != '/') || (spt->filename[strlen((char *)spt->filename) - 1] == '/') || strstr((char *)spt->filename, "/../")) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* only allow exported prefixes */ if (!tftp_prefix) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* check if the file exists */ if (tftp_read_data(spt, 0, spt->filename, 0) < 0) { tftp_send_error(spt, 1, "File not found", tp); return; } if (src[n - 1] != 0) { tftp_send_error(spt, 2, "Access violation", tp); return; } while (k < n) { const char *key, *value; key = (char *)src + k; k += strlen(key) + 1; if (k >= n) { tftp_send_error(spt, 2, "Access violation", tp); return; } value = (char *)src + k; k += strlen(value) + 1; if (strcmp(key, "tsize") == 0) { int tsize = atoi(value); struct stat stat_p; if (tsize == 0 && tftp_prefix) { char buffer[1024]; int len; len = snprintf(buffer, sizeof(buffer), "%s/%s", tftp_prefix, spt->filename); if (stat(buffer, &stat_p) == 0) tsize = stat_p.st_size; else { tftp_send_error(spt, 1, "File not found", tp); return; } } tftp_send_oack(spt, "tsize", tsize, tp); } } tftp_send_data(spt, 1, tp); }
/* Transfer a file to or from the server. */ int tftp_transfer(struct tftp_conn *tc) { int retval = 0; int len; int reclen; int totlen = 0; int terminate = 0; struct timeval timeout; /* Sanity check */ if (!tc) return -1; fd_set sfd; char recbuf[MSGBUF_SIZE]; FD_ZERO(&sfd); FD_SET(tc->sock, &sfd); len = BLOCK_SIZE + TFTP_DATA_HDR_LEN; reclen = 0; /* After the connection request we should start receiving data * immediately */ /* Set a timeout for resending data. */ timeout.tv_sec = TFTP_TIMEOUT; timeout.tv_usec = 0; /* Check if we are putting a file or getting a file and send * the corresponding request. */ if (tc->type == TFTP_TYPE_GET) { /* Send read request */ if(tftp_send_rrq(tc) < 0) fprintf(stderr,"FAIL TO SEND RRQ\n"); } else if (tc->type == TFTP_TYPE_PUT) { /* Send write request */ if(tftp_send_wrq(tc) < 0) fprintf(stderr,"FAIL TO SEND WRQ\n"); } else { return -1; } /* Put or get the file, block by block, in a loop. */ do { /* 1. Wait for something from the server (using * 'select'). If a timeout occurs, resend last block * or ack depending on whether we are in put or get * mode. */ /* ... */ printf("Waiting for response... \n"); FD_ZERO(&sfd); FD_SET(tc->sock, &sfd); switch (select(tc->sock + 1, &sfd, NULL, NULL, &timeout)) { case (-1): fprintf(stderr, "\nselect()\n"); break; case (0): /* Timeout, reinit the counter and do a resend. * Nested switch-case statemens are awesome! */ printf("**** TIMEOUT *****\n"); timeout.tv_sec = TFTP_TIMEOUT; timeout.tv_usec = 0; switch (ntohs(((u_int16_t*) tc->msgbuf)[0])) { case OPCODE_RRQ: tftp_send_rrq(tc); continue; case OPCODE_WRQ: tftp_send_wrq(tc); continue; case OPCODE_DATA: tftp_send_data(tc, -len); continue; case OPCODE_ACK: tftp_send_ack(tc); continue; case OPCODE_ERR: //TODO: Vilka error-medelanden ska skickas om, om några? continue; default: fprintf(stderr, "\nThis shouldn't happend\n"); goto out; //continue; } break; default: //TODO: Använda recvfrom() istället och kolla efter felaktig source port. /* Save the recieved bytes in 'rec_len' so we * can check if we should terminate the transfer */ printf("GOT SOMETHING!!!!\n"); reclen = recvfrom(tc->sock, recbuf, MSGBUF_SIZE,0, (struct sockaddr *) &tc->peer_addr, &tc->addrlen); print_message((struct tftp_msg *)recbuf, 1); //printf("%d\n", ntohs(((u_int16_t*) recbuf)[0])); break; } /* 2. Check the message type and take the necessary * action. */ switch (ntohs(((u_int16_t*) recbuf)[0])) { case OPCODE_DATA: /* Received data block, send ack */ //TODO: Skriv datan till en fil printf("Received data\n"); tc->blocknr++; if (tc->type == TFTP_TYPE_PUT) { fprintf(stderr, "\nExpected ack, got data\n"); goto out; } printf("We expect block number %d\n", tc->blocknr); printf("We got block number %d\n",ntohs(((u_int16_t*) recbuf)[1])); if (ntohs(((u_int16_t*) recbuf)[1]) != tc->blocknr) { fprintf(stderr, "\nGot unexpected data block§ nr\n"); goto out; } /* If we are getting and recieved a data package with * a block of < 512, we want to terminate the loop * after getting sending an ack */ if (reclen < (TFTP_DATA_HDR_LEN + BLOCK_SIZE)) terminate = 1; tftp_send_ack(tc); //TODO: Kolla returvärdet int i = 0; int hnllen = sizeof(HOST_NEWLINE_STYLE) - 1; int nanllen = sizeof(NETASCII_NEWLINE_STYLE) - 1; if (!strcmp(tc->mode, MODE_NETASCII)) { do { if (!strncmp(&recbuf[TFTP_DATA_HDR_LEN + i], NETASCII_NEWLINE_STYLE, nanllen)) { fwrite(HOST_NEWLINE_STYLE, 1, hnllen, tc->fp); i += nanllen; } else { fwrite(&recbuf[TFTP_DATA_HDR_LEN + i], 1, 1,tc->fp); i++; } } while(i < reclen - TFTP_DATA_HDR_LEN); } else { fwrite(&recbuf[TFTP_DATA_HDR_LEN],1,reclen - TFTP_DATA_HDR_LEN,tc->fp); } break; case OPCODE_ACK: printf("Received ACK, send next block\n"); if (tc->type == TFTP_TYPE_GET) { fprintf(stderr, "\nExpected data, got ack\n"); goto out; } /* If we are putting and sent a data package with * a block of < 512 bytes last time, we want to * terminate the loop after getting the final ack */ if (len < (TFTP_DATA_HDR_LEN + BLOCK_SIZE)) { terminate = 1; printf("We're done sending, let's terminate\n"); } else { /* Save the numer of sent bytes in 'len' in case * it's < 512 and the package has to be resent. */ len = tftp_send_data(tc, BLOCK_SIZE); printf("We sent a packet of length %d\n",len); } break; case OPCODE_ERR: printf("The transfer was terminated with an error "); printf("and the pitiful excuse given by the server was: "); printf("%s\n", ((struct tftp_err*) &recbuf)->errmsg); break; default: fprintf(stderr, "\nUnknown message type\n"); goto out; } totlen += (len + reclen); } while (!terminate); if (tc->type == TFTP_TYPE_GET && terminate) { /* The loop terminated succesfully but the last ack might * have been lost. Wait for a possible duplicate data * package and in that case send the ack one more time */ timeout.tv_sec = TFTP_TIMEOUT; timeout.tv_usec = 0; if (select(1, &sfd, NULL, NULL, &timeout) > 0) { read(tc->sock, tc->msgbuf, MSGBUF_SIZE); if (ntohs(((u_int16_t *) tc->msgbuf)[0]) == OPCODE_DATA && ntohs(((u_int16_t*) tc->msgbuf)[1]) == tc->blocknr) { tftp_send_ack(tc); } } } printf("\nTotal data bytes sent/received: %d.\n", totlen); out: fclose(tc->fp); return retval; }
static void tftp_handle_rrq(struct tftp_t *tp, int pktlen) { struct tftp_session *spt; int s, k, n; u_int8_t *src, *dst; s = tftp_session_allocate(tp); if (s < 0) { return; } spt = &tftp_sessions[s]; src = tp->x.tp_buf; dst = (u_int8_t *)spt->filename; n = pktlen - ((uint8_t *)&tp->x.tp_buf[0] - (uint8_t *)tp); /* get name */ for (k = 0; k < n; k++) { if (k < TFTP_FILENAME_MAX) { dst[k] = src[k]; } else { return; } if (src[k] == '\0') { break; } } if (k >= n) { return; } k++; /* check mode */ if ((n - k) < 6) { return; } if (memcmp(&src[k], "octet\0", 6) != 0) { tftp_send_error(spt, 4, "Unsupported transfer mode", tp); return; } /* do sanity checks on the filename */ if ((spt->filename[0] != '/') || (spt->filename[strlen(spt->filename) - 1] == '/') || strstr(spt->filename, "/../")) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* only allow exported prefixes */ if (!tftp_prefix || (strncmp(spt->filename, tftp_prefix, strlen(tftp_prefix)) != 0)) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* check if the file exists */ if (tftp_read_data(spt, 0, (u_int8_t *)spt->filename, 0) < 0) { tftp_send_error(spt, 1, "File not found", tp); return; } tftp_send_data(spt, 1, tp); }
/* Transfer a file to or from the server. */ int tftp_transfer(struct tftp_conn *tc) { int retval = 0; int len; int totlen = 0; struct timeval timeout; int loopend = 1; int final_pkt = 0; int first_rrq = 1; int first_wrq = 1; /* Sanity check */ if (!tc) return -1; len = 0; /* ===ADDED (NOT ACCORDING TO INSTRUCTIONS)=== */ long int file_size; fseek(tc->fp, 0L, SEEK_END); file_size = ftell(tc->fp); fseek(tc->fp, 0L, SEEK_SET); /* ===END OF ADDED=== */ /* After the connection request we should start receiving data * immediately */ /* Set a timeout for resending data. */ timeout.tv_sec = TFTP_TIMEOUT; timeout.tv_usec = 0; /* Check if we are putting a file or getting a file and send * the corresponding request. */ /* ===ADDED=== */ if (tc->type == TFTP_TYPE_GET) { retval = tftp_send_rrq(tc); } else if (tc->type == TFTP_TYPE_PUT) { retval = tftp_send_wrq(tc); } if (retval == -1) { return retval; } else { totlen += retval; } /* ===END OF ADDED=== */ /* Put or get the file, block by block, in a loop. */ int all_done = 0; do { /* 1. Wait for something from the server (using * 'select'). If a timeout occurs, resend last block * or ack depending on whether we are in put or get * mode. */ /* ===ADDED=== */ fd_set readfds; FD_ZERO(&readfds); FD_SET(tc->sock, &readfds); select(tc->sock + 1, &readfds, NULL, NULL, &timeout); if (FD_ISSET(tc->sock, &readfds)) { retval = recvfrom(tc->sock, tc->msgbuf, MSGBUF_SIZE, 0, (struct sockaddr *) &tc->peer_addr, &tc->addrlen); } else { // Timeout if (tc->type == TFTP_TYPE_PUT) { if (first_wrq == 1) { retval = tftp_send_wrq(tc); } else { retval = tftp_send_data(tc, -len); } continue; } else if (tc->type == TFTP_TYPE_GET) { if (first_rrq == 1) { retval = tftp_send_rrq(tc); } else if(final_pkt == 1) { break; } else { retval = tftp_send_ack(tc); } continue; } } /* ===END OF ADDED=== */ /* 2. Check the message type and take the necessary * action. */ /* ===ADDED/CHANGED=== */ u_int16_t received_opcode = htons(*((u_int16_t*)tc->msgbuf)); u_int16_t received_blocknr; struct tftp_data *data; struct tftp_ack *ack_recv; struct tftp_err *err_recv; switch (received_opcode) { case OPCODE_DATA: /* Received data block, send ack */ data = (struct tftp_data *) tc->msgbuf; totlen += retval; len = retval - TFTP_DATA_HDR_LEN; received_blocknr = ntohs(data->blocknr); if((tc->blocknr+1) == received_blocknr){ fwrite(data->data, 1, len, tc->fp); if(len < BLOCK_SIZE) { final_pkt = 1; } tc->blocknr++; } retval = tftp_send_ack(tc); first_rrq = 0; break; case OPCODE_ACK: /* Received ACK, send next block */ ack_recv = (struct tftp_ack *) tc->msgbuf; received_blocknr = ntohs(ack_recv->blocknr); if(received_blocknr == tc->blocknr){ if(all_done){ loopend = 0; } else { tc->blocknr++; long int file_pos = ftell(tc->fp); if (file_size - file_pos < BLOCK_SIZE) { retval = tftp_send_data(tc, file_size - file_pos); all_done = 1; } else { retval = tftp_send_data(tc, BLOCK_SIZE); } } } else { retval = tftp_send_data(tc, -len); } len = retval; first_wrq = 0; break; case OPCODE_ERR: err_recv = (struct tftp_err *) tc->msgbuf; printf("ERROR CODE %d: %s\n", ntohs(err_recv->errcode), err_recv->errmsg); goto out; /* ===END OF ADDED/CHANGED=== */ default: fprintf(stderr, "\nUnknown message type\n"); goto out; } /* ===ADDED=== */ totlen += retval; /* ===END OF ADDED=== */ /* ===CHANGED=== */ } while (loopend /* 3. Loop until file is finished */); /* ===END OF CHANGED=== */ printf("\nTotal data bytes sent/received: %d.\n", totlen); out: return retval; }