END_TEST START_TEST(tc_tftp_send_error) { char longtext[1024]; example_session.socket = &example_socket; called_user_cb = 0; called_pico_socket_close = 0; /* Sending empty msg */ called_sendto = 0; expected_opcode = TFTP_ERROR; tftp_send_error(&example_session, NULL, 0, 0, NULL); fail_if(called_sendto < 1); /* Sending some msg */ called_sendto = 0; expected_opcode = TFTP_ERROR; tftp_send_error(&example_session, NULL, 0, 0, "some text here"); fail_if(called_sendto < 1); /* sending some very long msg */ memset(longtext, 'a', 1023); longtext[1023] = (char)0; called_sendto = 0; expected_opcode = TFTP_ERROR; tftp_send_error(&example_session, NULL, 0, 0, longtext); fail_if(called_sendto < 1); }
static int tftp_send_data(struct tftp_session *spt, u_int16_t block_nr, struct tftp_t *recv_tp) { struct sockaddr_in saddr, daddr; struct mbuf *m; struct tftp_t *tp; int nobytes; if (block_nr < 1) { return -1; } m = m_get(spt->slirp); if (!m) { return -1; } memset(m->m_data, 0, m->m_size); m->m_data += IF_MAXLINKHDR; tp = (void *)m->m_data; m->m_data += sizeof(struct udpiphdr); tp->tp_op = htons(TFTP_DATA); tp->x.tp_data.tp_block_nr = htons(block_nr); saddr.sin_addr = recv_tp->ip.ip_dst; saddr.sin_port = recv_tp->udp.uh_dport; daddr.sin_addr = spt->client_ip; daddr.sin_port = spt->client_port; nobytes = tftp_read_data(spt, block_nr - 1, tp->x.tp_data.tp_buf, 512); if (nobytes < 0) { m_free(m); /* send "file not found" error back */ tftp_send_error(spt, 1, "File not found", tp); return -1; } m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - sizeof(struct ip) - sizeof(struct udphdr); udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); if (nobytes == 512) { tftp_session_update(spt); } else { tftp_session_terminate(spt); } return 0; }
static int tftp_send_data(PNATState pData, struct tftp_session *spt, u_int16_t block_nr, struct tftp_t *recv_tp) { struct sockaddr_in saddr, daddr; struct mbuf *m; struct tftp_t *tp; int nobytes; if (block_nr < 1) return -1; m = slirpTftpMbufAlloc(pData); if (!m) return -1; m->m_data += if_maxlinkhdr; m->m_pkthdr.header = mtod(m, void *); tp = mtod(m, void *); m->m_data += sizeof(struct udpiphdr); tp->tp_op = RT_H2N_U16_C(TFTP_DATA); tp->x.tp_data.tp_block_nr = RT_H2N_U16(block_nr); saddr.sin_addr = recv_tp->ip.ip_dst; saddr.sin_port = recv_tp->udp.uh_dport; daddr.sin_addr = spt->client_ip; daddr.sin_port = spt->client_port; nobytes = tftp_read_data(pData, spt, block_nr - 1, tp->x.tp_data.tp_buf, 512); if (nobytes < 0) { m_freem(pData, m); /* send "file not found" error back */ tftp_send_error(pData, spt, 1, "File not found", tp); return -1; } m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - sizeof(struct ip) - sizeof(struct udphdr); udp_output2(pData, NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); if (nobytes == 512) tftp_session_update(pData, spt); else tftp_session_terminate(spt); return 0; }
static void tftp_send_next_block(struct tftp_session *spt, struct tftp_t *recv_tp) { struct mbuf *m; struct tftp_t *tp; int nobytes; m = m_get(spt->slirp); if (!m) { return; } tp = tftp_prep_mbuf_data(spt, m); tp->tp_op = htons(TFTP_DATA); tp->x.tp_data.tp_block_nr = htons((spt->block_nr + 1) & 0xffff); nobytes = tftp_read_data(spt, spt->block_nr, tp->x.tp_data.tp_buf, spt->block_size); if (nobytes < 0) { m_free(m); /* send "file not found" error back */ tftp_send_error(spt, 1, "File not found", tp); return; } m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX - nobytes) - sizeof(struct udphdr); tftp_udp_output(spt, m, recv_tp); if (nobytes == spt->block_size) { tftp_session_update(spt); } else { tftp_session_terminate(spt); } spt->block_nr++; }
/* * Receive a file. This is implemented as a state machine using a while loop * and a switch statement. This follow pxe specification pages 32-34. */ int tftp_mtftp_receive_file(struct client_data *data) { int state = S_SEND_REQ; /* current state in the state machine */ int timeout_state = state; /* what state should we go on when timeout */ int result; int block_number = 0; int last_block_number = -1;/* block number of last block for multicast */ int data_size; /* size of data received */ int sockfd = data->sockfd; /* just to simplify calls */ int sock; struct sockaddr_storage sa; /* a copy of data.sa_peer */ struct sockaddr_storage from; char from_str[SOCKADDR_PRINT_ADDR_LEN]; struct tftphdr *tftphdr = (struct tftphdr *)data->data_buffer; FILE *fp = NULL; /* the local file pointer */ int number_of_timeout = 0; int timeout = 0; struct sockaddr_storage sa_mcast_group; int mcast_sockfd = 0; struct sockaddr_storage sa_mcast; union ip_mreq_storage mreq; struct addrinfo hints, *addrinfo; int err; int mode = LISTEN; unsigned int file_bitmap[NB_BLOCK]; char string[MAXLEN]; data->file_size = 0; tftp_cancel = 0; memset(&from, 0, sizeof(from)); memset(&sa_mcast, 0, sizeof(struct sockaddr_storage)); memset(&file_bitmap, 0, sizeof(file_bitmap)); /* make sure the socket is not connected */ sa.ss_family = AF_UNSPEC; connect(sockfd, (struct sockaddr *)&sa, sizeof(sa)); /* copy sa_peer structure */ memcpy(&sa, &data->sa_peer, sizeof(sa)); /* check to see if conversion is requiered */ if (strcasecmp(data->tftp_options[OPT_MODE].value, "netascii") == 0) fprintf(stderr, "netascii convertion ignored\n"); /* make sure the data buffer is SEGSIZE + 4 bytes */ if (data->data_buffer_size != (SEGSIZE + 4)) { data->data_buffer = realloc(data->data_buffer, SEGSIZE + 4); tftphdr = (struct tftphdr *)data->data_buffer; if (data->data_buffer == NULL) { fprintf(stderr, "atftp: memory allocation failure.\n"); exit(1); } data->data_buffer_size = SEGSIZE + 4; } /* open the file for writing */ if ((fp = fopen(data->local_file, "w")) == NULL) { fprintf(stderr, "atftp: can't open %s for writing.\n", data->local_file); return ERR; } /* Configure multicast stuff, look up the host */ /* if valid, update s_inn structure */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; if (!getaddrinfo(data->mtftp_mcast_ip, NULL, &hints, &addrinfo) && !sockaddr_set_addrinfo(&sa_mcast_group, addrinfo)) { freeaddrinfo(addrinfo); if (!sockaddr_is_multicast(&sa_mcast_group)) { fprintf(stderr, "mtftp: bad multicast address %s\n", data->mtftp_mcast_ip); exit(1); } } else { fprintf(stderr, "atftp: bad multicast address %s", data->mtftp_mcast_ip); exit(1); } /* we need to open a new socket for multicast */ if ((mcast_sockfd = socket(AF_INET, SOCK_DGRAM, 0))<0) { perror("atftp: socket"); exit(1); } memset(&sa_mcast, 0, sizeof(sa_mcast)); sa_mcast.ss_family = sa_mcast_group.ss_family; sockaddr_set_port(&sa, data->mtftp_client_port); if (bind(mcast_sockfd, (struct sockaddr *)&sa_mcast, sizeof(sa_mcast)) < 0) { perror("atftp: bind"); exit(1); } sockaddr_get_mreq(&sa_mcast_group, &mreq); if (sa_mcast_group.ss_family == AF_INET) err = setsockopt(mcast_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq.v4, sizeof(mreq.v4)); else err = setsockopt(mcast_sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq.v6, sizeof(mreq.v6)); if (err < 0) { perror("atftp: setsockopt"); exit(1); } state = S_LISTEN; while (1) { #ifdef DEBUG if (data->delay) usleep(data->delay*1000); #endif if (tftp_cancel) { if (from.ss_family == 0) state = S_ABORT; else { if (mode == RECEIVE) { tftp_send_error(sockfd, &sa, EUNDEF, data->data_buffer, data->data_buffer_size); if (data->trace) fprintf(stderr, "sent ERROR <code: %d, msg: %s>\n", EUNDEF, tftp_errmsg[EUNDEF]); } state = S_ABORT; } tftp_cancel = 0; } switch (state) { case S_LISTEN: if (data->trace) fprintf(stderr, "mtftp: listening for ongoing transfer on %s port %d\n", data->mtftp_mcast_ip, data->mtftp_client_port); number_of_timeout = 0; mode = LISTEN; if (last_block_number > 0) { timeout = data->mtftp_listen_delay - tftp_mtftp_missed_packet(file_bitmap, last_block_number, 1); if (timeout < 0) timeout = 0; } else timeout = data->mtftp_listen_delay; state = S_WAIT_PACKET; timeout_state = S_OPEN; break; case S_OPEN: if (data->trace) fprintf(stderr, "mtftp: opening new connection\n"); mode = OPEN; block_number = 0; timeout = data->mtftp_timeout_delay; state = S_SEND_REQ; break; case S_RECEIVE: if (data->trace) fprintf(stderr, "mtftp: connected, receiving\n"); mode = RECEIVE; timeout = data->mtftp_timeout_delay; state = S_SEND_ACK; break; case S_SEND_REQ: timeout_state = S_SEND_REQ; if (data->trace) { opt_options_to_string(data->tftp_options, string, MAXLEN); fprintf(stderr, "sent RRQ <file: %s, mode: %s <%s>>\n", data->tftp_options[OPT_FILENAME].value, data->tftp_options[OPT_MODE].value, string); } /* send request packet */ if (tftp_send_request(sockfd, &sa, RRQ, data->data_buffer, data->data_buffer_size, data->tftp_options) == ERR) state = S_ABORT; else state = S_WAIT_PACKET; sockaddr_set_port(&sa, 0); /* must be set to 0 before the fist call to tftp_get_packet, but it was set before the call to tftp_send_request with the server port */ break; case S_SEND_ACK: timeout_state = S_SEND_ACK; /* walk the bitmap to find the next missing block */ //prev_bitmap_hole = //tftp_find_bitmap_hole(prev_bitmap_hole, file_bitmap); //block_number = prev_bitmap_hole; if (data->trace) fprintf(stderr, "sent ACK <block: %d>\n", block_number); tftp_send_ack(sockfd, &sa, block_number); /* if we just ACK the last block we are done */ if (block_number == last_block_number) state = S_END; else state = S_WAIT_PACKET; break; case S_WAIT_PACKET: data_size = data->data_buffer_size; /* receive the data */ result = tftp_get_packet(sockfd, mcast_sockfd, &sock, &sa, &from, NULL, timeout, &data_size, data->data_buffer); switch (result) { case GET_TIMEOUT: number_of_timeout++; if (mode == LISTEN) { fprintf(stderr, "mtftp: timeout while listening\n"); state = S_OPEN; } else { fprintf(stderr, "mtftp: timeout: retrying...\n"); if (number_of_timeout > NB_OF_RETRY) state = S_ABORT; else state = timeout_state; } break; case GET_ERROR: if (mode == LISTEN) { fprintf(stderr, "mtftp: unexpected error received from server, ignoring\n"); break; } /* Can only receive this error from unicast */ if (!sockaddr_equal_addr(&sa, &from)) { fprintf(stderr, "mtftp: error packet discarded from <%s>.\n", sockaddr_print_addr(&from, from_str, sizeof(from_str))); break; } /* packet is for us */ fprintf(stderr, "mtftp: error received from server"); fwrite(tftphdr->th_msg, 1, data_size - 4 - 1, stderr); fprintf(stderr, ">\n"); state = S_ABORT; break; case GET_DATA: /* Specification state that server source IP must matches, but port is not a requierement (anyway we may not know the source port yet) */ if (sockaddr_equal_addr(&sa, &from)) { if (mode != LISTEN) { if (sock == sockfd) { /* This is a unicast packet from the server. This should happend for the first data block only, when current block number is 0 and when in OPEN mode */ if ((block_number > 0) || (mode != OPEN)) fprintf(stderr, "mtftp: unexpected unicast packet from <%s>," " continuing\n", sockaddr_print_addr(&from, from_str, sizeof(from_str))); else mode = RECEIVE; } else { /* We receive data on the multicast socket, it should happend for packets 1 and above */ if (block_number == 0) { mode = LISTEN; fprintf(stderr, "mtftp: got multicast data packet," " falling back to listen mode\n"); } } } else { /* We are in listenning mode, we expect data on multicast socket only */ if (sock == sockfd) { fprintf(stderr, "mtftp: unexpected unicast packet from <%s>.\n", sockaddr_print_addr(&from, from_str, sizeof(from_str))); break; } } } else { fprintf(stderr, "mtftp: unexpected packet from <%s>\n", sockaddr_print_addr(&from, from_str, sizeof(from_str))); break; } number_of_timeout = 0; state = S_DATA_RECEIVED; break; case GET_DISCARD: /* consider discarded packet as timeout to make sure when don't lock up when doing multicast transfer and routing is broken or when using wrong mcast IP or port */ number_of_timeout++; fprintf(stderr, "mtftp: packet discard <%s>.\n", sockaddr_print_addr(&from, from_str, sizeof(from_str))); if (number_of_timeout > NB_OF_RETRY) state = S_ABORT; break; case ERR: fprintf(stderr, "mtftp: unknown error.\n"); state = S_ABORT; break; default: fprintf(stderr, "mtftp: abnormal return value %d.\n", result); } break; case S_DATA_RECEIVED: block_number = ntohs(tftphdr->th_block); if (data->trace) fprintf(stderr, "received DATA <block: %d, size: %d>\n", ntohs(tftphdr->th_block), data_size - 4); fseek(fp, (block_number - 1) * (data->data_buffer_size - 4), SEEK_SET); if (fwrite(tftphdr->th_data, 1, data_size - 4, fp) != (data_size - 4)) { fprintf(stderr, "mtftp: error writing to file %s\n", data->local_file); if (mode == RECEIVE) tftp_send_error(sockfd, &sa, ENOSPACE, data->data_buffer, data->data_buffer_size); state = S_END; break; } data->file_size += data_size; /* FIXME: not usefull */ /* Record the block number of the last block. The last block is the one with less data than the transfer block size */ if (data_size < data->data_buffer_size) last_block_number = block_number; /* Mark the received block in the bitmap */ file_bitmap[(block_number - 1)/32] |= (1 << ((block_number - 1) % 32)); /* if we are the master client we ack, else we just wait for data */ if (mode == LISTEN) { /* If we've not received all packets, continue listen. In the case we've not seen the last packet yet, no choice but continuing listen phase and eventually fall back to the open mode and download the whole file again. If we've seen the last packet, we also continue listen, but if we've got all the file we are done */ if (last_block_number < 0) state = S_WAIT_PACKET; else { if (tftp_mtftp_missed_packet(file_bitmap, last_block_number, 0)) state = S_WAIT_PACKET; else { fprintf(stderr, "mtftp: got all packets\n"); state = S_END; } } } else state = S_SEND_ACK; break; case S_END: case S_ABORT: /* close file */ if (fp) fclose(fp); /* drop multicast membership */ if (sa_mcast_group.ss_family == AF_INET) err = setsockopt(mcast_sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq.v4, sizeof(mreq.v4)); else err = setsockopt(mcast_sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq.v6, sizeof(mreq.v6)); if (err < 0) { perror("setsockopt"); exit(1); } /* close socket */ if (mcast_sockfd) close(mcast_sockfd); /* return proper error code */ if (state == S_END) return OK; else fprintf(stderr, "mtftp: aborting\n"); default: return ERR; } } }
/* * 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; } } }
/* * Receive 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 ACK or OACK * 2) wait replay * - if DATA packet, read it, send an acknoledge, goto 2 * - if ERROR abort * - if TIMEOUT goto previous state */ int tftpd_receive_file(struct thread_data *data) { int state = S_BEGIN; int timeout_state = state; int result; int block_number = 0; int data_size; int sockfd = data->sockfd; struct sockaddr_storage *sa = &data->client_info->client; struct sockaddr_storage from; char addr_str[SOCKADDR_PRINT_ADDR_LEN]; 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 all_blocks_received = 0; /* temporary kludge */ int convert = 0; /* if true, do netascii convertion */ int prev_block_number = 0; /* needed to support netascii convertion */ 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; } /* Open the file for writing. */ if ((fp = fopen(filename, "w")) == NULL) { /* Can't create the file. */ logger(LOG_INFO, "Can't open %s for writing", filename); 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; } /* tsize option */ if (((result = opt_get_tsize(data->tftp_options)) > -1) && !convert) { opt_set_tsize(result, data->tftp_options); logger(LOG_DEBUG, "tsize option -> %d", result); } /* 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_DEBUG, "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_DEBUG, "blksize option -> %d", result); } /* that's it, we start receiving the file */ while (1) { if (tftpd_cancel) { logger(LOG_DEBUG, "thread cancelled"); tftp_send_error(sockfd, sa, EUNDEF, data->data_buffer, data->data_buffer_size); if (data->trace) logger(LOG_DEBUG, "sent ERROR <code: %d, msg: %s>", EUNDEF, tftp_errmsg[EUNDEF]); state = S_ABORT; } switch (state) { case S_BEGIN: /* Did the client request RFC1350 options ?*/ if (opt_support_options(data->tftp_options)) state = S_SEND_OACK; else state = S_SEND_ACK; break; case S_SEND_ACK: timeout_state = state; tftp_send_ack(sockfd, sa, block_number); if (data->trace) logger(LOG_DEBUG, "sent ACK <block: %d>", block_number); if (all_blocks_received) state = S_END; else state = S_WAIT_PACKET; break; case S_SEND_OACK: timeout_state = state; tftp_send_oack(sockfd, sa, data->tftp_options, data->data_buffer, data->data_buffer_size); opt_options_to_string(data->tftp_options, string, MAXLEN); if (data->trace) logger(LOG_DEBUG, "sent OACK <%s>", string); 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(&data->client_info->client, addr_str, sizeof(addr_str))); state = S_END; } else { logger(LOG_WARNING, "timeout: retrying..."); state = timeout_state; } break; case GET_ERROR: /* * This does not work correctly if load balancers are * employed on one side of a multihomed host. ie, the * tftp RRQ goes through the load balancer and has the * source ports changed while the multicast traffic * bypasses the load balancers. * * **** It is not compliant with RFC1350 to skip this * **** test since the port number is the TID. Use this * **** only if you know what you're doing. */ 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"); } 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); state = S_ABORT; break; case GET_DATA: /* Check that source port match */ 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"); } number_of_timeout = 0; state = S_DATA_RECEIVED; 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); state = S_ABORT; } break; case S_DATA_RECEIVED: /* We need to seek to the right place in the file */ block_number = ntohs(tftphdr->th_block); if (data->trace) logger(LOG_DEBUG, "received DATA <block: %d, size: %d>", block_number, data_size - 4); if (tftp_file_write(fp, tftphdr->th_data, data->data_buffer_size - 4, block_number, data_size - 4, convert, &prev_block_number, &temp) != data_size - 4) { logger(LOG_ERR, "%s: %d: error writing to file %s", __FILE__, __LINE__, filename); 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]); state = S_ABORT; break; } if (data_size < data->data_buffer_size) all_blocks_received = 1; else all_blocks_received = 0; state = S_SEND_ACK; break; case S_END: fclose(fp); return OK; case S_ABORT: fclose(fp); return ERR; default: fclose(fp); logger(LOG_ERR, "%s: %d: tftpd_file.c: huh?", __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); }
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; const char *option_name[2]; uint32_t option_value[2]; int nb_options = 0; /* check if a session already exists and if so terminate it */ s = tftp_session_find(slirp, tp); if (s >= 0) { tftp_session_terminate(&slirp->tftp_sessions[s]); } s = tftp_session_allocate(slirp, tp); if (s < 0) { return; } spt = &slirp->tftp_sessions[s]; /* unspecified prefix means service disabled */ if (!slirp->tftp_prefix) { tftp_send_error(spt, 2, "Access violation", tp); return; } /* skip header fields */ k = 0; pktlen -= offsetof(struct tftp_t, x.tp_buf); /* prepend tftp_prefix */ prefix_len = strlen(slirp->tftp_prefix); spt->filename = g_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] = 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 (strcasecmp(&tp->x.tp_buf[k], "octet") != 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 && nb_options < ARRAY_SIZE(option_name)) { const char *key, *value; key = &tp->x.tp_buf[k]; k += strlen(key) + 1; if (k >= pktlen) { tftp_send_error(spt, 2, "Access violation", tp); return; } value = &tp->x.tp_buf[k]; k += strlen(value) + 1; if (strcasecmp(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; } } option_name[nb_options] = "tsize"; option_value[nb_options] = tsize; nb_options++; } else if (strcasecmp(key, "blksize") == 0) { int blksize = atoi(value); /* If blksize option is bigger than what we will * emit, accept the option with our packet size. * Otherwise, simply do as we didn't see the option. */ if (blksize >= 512) { option_name[nb_options] = "blksize"; option_value[nb_options] = 512; nb_options++; } } } if (nb_options > 0) { assert(nb_options <= ARRAY_SIZE(option_name)); tftp_send_oack(spt, option_name, option_value, nb_options, tp); return; } spt->block_nr = 0; tftp_send_next_block(spt, 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 = (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); }