static void handle_oack_transmit(tftp_client_state_t *state) { int len; len = snprintf(state->block_buffer + 2, sizeof(TFTP_OPT_BLKSIZE) + 1, "%s", TFTP_OPT_BLKSIZE); len += snprintf(state->block_buffer + len + 3, 5 + 1, "%u", state->opt_blocksize); state->block_buffer_size = 2 + len + 2; send_oack(state->client, state->block_buffer, state->block_buffer_size); }
/* * WRQ - receive a file from the client */ void tftp_wrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strlcpy(fnbuf, filename, sizeof(fnbuf)); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, WRQ); if (ecode == 0) { if (has_options) send_oack(peer); else send_ack(peer, 0); } if (logging) { tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, filename, errtomsg(ecode)); } if (ecode) { send_error(peer, ecode); exit(1); } tftp_recvfile(peer, mode); exit(0); }
/****************************************************************************** Start obsluhy klienta Inspirace (vytvoreni socketu, bind): http://beej.us/guide/bgnet/output/html/multipage/clientserver.html#datagram *******************************************************************************/ int Client::start(Opt *options, struct sockaddr_storage client_addr, int index, char *buf, int numbytes, int mtu) { // Generovani nahodneho cisla pro port, na kterem bude klient obsluhovan srand (getpid()); int port = rand() % MAX_PORT + MIN_PORT; client_port = to_string(port); service_ip = options->address_at(index); int sockfd; struct addrinfo hints, *servinfo, *p; int rv; socklen_t addr_len; char s[INET6_ADDRSTRLEN]; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; signal(SIGINT, client_signal_reaction); // Registrace funkce pro odchyt signalu if ((rv = getaddrinfo(service_ip.c_str(), client_port.c_str(), &hints, &servinfo)) != 0) { cerr << "Error - Service " << service_ip << ", " << client_port << gai_strerror(rv) << endl; return EXIT_FAILURE; } for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { continue; } if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); continue; } break; } if (p == NULL) { cerr << "Error - Service " << service_ip << ", " << client_port << " - failed to bind socket" << endl; return EXIT_FAILURE; } freeaddrinfo(servinfo); client_socket = sockfd; // Ulozim si deskr. socketu // Nastavi se vychozi timeout socketu if((set_socket_timeout(options, IMPLICIT_TIMEOUT)) == SET_FAILED) return EXIT_FAILURE; // ulozim si klientovu IP pro potreby vypisu informacnich hlasek string client_ip = inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *)&client_addr), s, sizeof s); mtu_size = mtu; // Ulozim si MTU // Cinnost obsluhy serveru string err_msg; unsigned int data_counter = 0; // Pocitadlo dat unsigned int block_counter = 0; // Pocitadlo bloku int data_len; // Pocet odeslanych B int mode; // Mod prenosu int tryouts = 0; // Pocet pokusu o znovu navazani komunikace bool reading_data = false; // Flag cteni dat bool writing_data = false; // Flag zapisu dat int ack; // Cislo potvrzeneho bloku int type; // Typ paketu int file_status; // Info o uspesnosti operace se souborem int bytes_write; // Pocet zapsanych bajtu int block_num; // Cislo bloku int recv_status; // Pocet prijatych B pri volani funkce recvfrom() char *file_buf; // Buffer pro nacitani dat ze souboru int file_buf_size;// Velikost bufferu File *f = new File(); // Nova instance objektu pro praci se soubory while(1) {// Smycka obsluhy klienta if(writing_data == true) type = packet_type(file_buf); // buffer na heapu else type = packet_type(buf); // buffer na zasobniku switch(type) {// Podle typu paketu se provadi dana operace case RRQ: {// Zadost o cteni get_filename(buf); // ukladani jmena souboru // Zjisti se mod prenosu mode = transfer_mode(buf); if(mode == UNKNOWN_MODE) {// neznamy mod err_msg = "Unknown mode"; send_error(sockfd, client_addr, err_msg, ERROR_IL); cout << current_time() << client_ip << " Unknown mode, operation aborted" << endl; clean_sources(f, sockfd); return 1; } else if(mode == NETASCII) { cout << current_time() + client_ip + " Requested READ of " << filename << " [netascii]"<< endl; } else if(mode == OCTET) { cout << current_time() + client_ip + " Requested READ of " << filename << " [octet]"<< endl; } // Otevre se soubor pro cteni file_status = f->open(filename, options->working_path(), FILE_IN, mode_tr); if(file_status == NOT_FOUND) {// Soubor nebyl nalezen err_msg = "File not found"; send_error(sockfd, client_addr, err_msg, ERROR_NF); cout << current_time() + client_ip + " File " << filename << " not found, operation aborted" << endl; clean_sources(f, sockfd); return 1; } else if(file_status == PERM_DEN) {// Nedostatecne opravneni err_msg = "Permission denided"; send_error(sockfd, client_addr, err_msg, ERROR_AV); cout << current_time() << client_ip << " Permission denided, operation aborted" << endl; clean_sources(f, sockfd); return EXIT_FAILURE; } // Zjisti se pritomnost rozsireni if(opt_extension(buf, numbytes, options, f) == EXTENSION) {// request obsahuje options send_oack(sockfd, client_addr); reading_data = true; // Alokace bufferu pro nacitani souboru if(block_size.used == true) {// byla specifikovana velikost bloku file_buf = new char[block_size.value + 1]; file_buf_size = block_size.value + 1; if(f->get_file_size() > MAX_BLOCK_COUNT * block_size.value) {// Soubor je prilis velky na prenos pres tftp err_msg = "File is too large to send"; send_error(sockfd, client_addr, err_msg, ERROR_DF); cout << current_time() + client_ip + " File is too large to send. Operation aborted\n"; clean_sources(f, sockfd); return EXIT_FAILURE; } } else {// mezi parametry nebyla velikost bloku, uvazuje se standardni file_buf = new char[STANDARD_BLOCK + 1]; file_buf_size = STANDARD_BLOCK + 1; } } else {// bez parametru, posilani prvnich dat if(f->get_file_size() > MAX_BLOCK_COUNT * STANDARD_BLOCK) {// Soubor je prilis velky na prenos pres tftp err_msg = "File is too large to send"; send_error(sockfd, client_addr, err_msg, ERROR_IL); cout << current_time() + client_ip + " File is too large to send. Operation aborted\n"; clean_sources(f, sockfd); return 1; } // Alokace bufferu pro nacitani souboru - standardni velikost file_buf = new char[STANDARD_BLOCK + 1]; reading_data = true; cout << current_time() + client_ip + " Sending DATA\n"; // poslou se prvni data data_len = send_data(sockfd, client_addr, 1, STANDARD_BLOCK, f, file_buf); data_counter += data_len; if(data_len < STANDARD_BLOCK) {// Prvni blok je zaroven i posledni, tim konci cinnost reading_data = false; } } break; } case WRQ: {// Zadost o zapis get_filename(buf); // ulozim jmeno souboru // Zjistim mod prenosu mode = transfer_mode(buf); if(mode == UNKNOWN_MODE) {// neznamy mod err_msg = "Unknown transfer mode"; send_error(sockfd, client_addr, err_msg, ERROR_IL); break; } else if(mode == NETASCII) { cout << current_time() << client_ip << " Requested WRITE of " << filename << " [netascii]" << endl; } else if(mode == OCTET) { cout << current_time() << client_ip << " Requested WRITE of " << filename << " [octet]" << endl; } // Otevre se soubor pro zapis if(mode == OCTET) { file_status = f->open(filename, options->working_path(), FILE_OUT, OCTET); } else if(mode == NETASCII) { file_status = f->open(filename, options->working_path(), FILE_OUT, NETASCII); } if(file_status == FILE_EXISTS) {// Soubor j*z existuje err_msg = "File already exists"; send_error(sockfd, client_addr, err_msg, ERROR_AE); cout << current_time() << client_ip << " File " << filename << " already exists, operation aborted" << endl; clean_sources(f, sockfd); return 1; } else if(file_status == CANNOT_OPEN) {// Nelze otevrit err_msg = "Cannot open file for writing"; send_error(sockfd, client_addr, err_msg, ERROR_IL); cout << current_time() << client_ip << " Cannot opet file " << filename << " for writing, operation aborted" << endl; clean_sources(f, sockfd); return 1; } // Zjisti se rozsireni if(opt_extension(buf, numbytes, options, f) == EXTENSION) {// request obsahuje options send_oack(sockfd, client_addr); // Alokace bufferu pro nacitani souboru if(block_size.used == true) {// byla specifikovana velikost bloku file_buf = new char[block_size.value + 10]; file_buf_size = block_size.value + 10; } else {// mezi parametry nebyla velikost bloku, uvazuje se standardni file_buf = new char[STANDARD_BLOCK + 10]; file_buf_size = STANDARD_BLOCK + 10; } writing_data = true; cout << current_time() + client_ip + " Receiving DATA\n"; } else {// Bez rozsireni, zasle se ack 0 send_ack(sockfd, client_addr, 0); // Alokace bufferu pro zapis souboru - standardni velikost file_buf = new char[STANDARD_BLOCK + 10]; file_buf_size = STANDARD_BLOCK + 10; writing_data = true; cout << current_time() + client_ip + " Receiving DATA\n"; } break; } case DATA: {// Datovy paket od klienta if(writing_data == true) {// Probiha prenos block_num = ack_number(file_buf); // zjisti se cislo bloku if((block_counter + 1) != block_num) {// Prisel blok, ktery nenavazuje na predchozi err_msg = "Error while file transfer"; send_error(sockfd, client_addr, err_msg, ERROR_IL); break; } int actual_block; if(block_size.used == true) {// Pouziva se nestandardni velikost bloku actual_block = block_size.value; bytes_write = recv_data(file_buf, numbytes, actual_block, f); // zapisou se data } else {// Standardni velikost bloku actual_block = STANDARD_BLOCK; bytes_write = recv_data(file_buf, numbytes, actual_block, f); // zapisou se data } if(bytes_write >= 0) {// Zapis byl uspesny, potvrdi se klientovi send_ack(sockfd, client_addr, block_num); block_counter++; // zvetsi se pocitadlo ulozenych bloku data_counter += bytes_write; // pricte se pocet ulozenych dat k pocitadlu if((numbytes - 4) < actual_block) {// dat bylo min nez je velikost bloku writing_data = false; f->close_file(FILE_OUT); // uzavre se soubor pro zapis cout << current_time() << client_ip << " File " << filename << " has been stored [" << data_counter << " B, " << block_counter << " blocks]" << endl; clean_sources(f, sockfd); return EXIT_FAILURE; } } else if(bytes_write == READ_ERR) {// Zapis nebyl uspesny err_msg = "Error while writing to file"; send_error(sockfd, client_addr, err_msg, ERROR_IL); clean_sources(f, sockfd); return EXIT_FAILURE; } } break; } case ACK: {// Potvrzeni od klienta, ze obdrzel konkretni datovy paket ack = ack_number(buf); if(ack == 0) cout << current_time() + client_ip + " Sending DATA\n"; if(reading_data == true) {// prenos jeste nebyl dokoncen if(block_size.used == true) {// Pouziva se nestandardni velikost bloku data_len = send_data(sockfd, client_addr, ack + 1, block_size.value, f, file_buf); if(data_len == SEND_FAIL) {// Chyba pri odesilani cerr << current_time() << client_ip << " Error in sendto, operation aborted" << endl; clean_sources(f, sockfd); return EXIT_FAILURE; } else if(data_len < block_size.value) reading_data = false; } else {// Standardni velikost bloku data_len = send_data(sockfd, client_addr, ack + 1, STANDARD_BLOCK, f, file_buf); if(data_len == SEND_FAIL) {// Chyba pri odesilani cerr << current_time() << client_ip << " Error in sendto, operation aborted" << endl; clean_sources(f, sockfd); return EXIT_FAILURE; } else if(data_len < STANDARD_BLOCK) reading_data = false; } data_counter += data_len; } else {// Prenos byl dokoncen cout << current_time() << client_ip << " File " << filename << " has been sent [" << data_counter << " B, " << ack << " blocks]\n"; clean_sources(f, sockfd); return EXIT_SUCCESS; } break; } case ERR: {// Error paket int err; if(reading_data == true) {// Klient poslal error pri cteni dat err = recv_error(buf); switch(err) { case ERROR_UN: {// Unknown transfer ID f->close_file(FILE_IN); cout << current_time() << client_ip << " Client aborted file read (transport error)" << endl; return EXIT_FAILURE; } case ERROR_DF: {// Disk full f->close_file(FILE_IN); cout << current_time() << client_ip << " Client aborted file read (too large)" << endl; return EXIT_FAILURE; } case ERROR_ND: {// Nedefinovana chyba f->close_file(FILE_IN); cout << current_time() << client_ip << " Client aborted file read (undefined error)" << endl; return EXIT_FAILURE; } } } else if(writing_data == true) {// Klient poslal error pri zapisu dat err = recv_error(buf); switch(err) { case ERROR_UN: {// Unknown transfer ID f->close_file(FILE_OUT); cout << current_time() << client_ip << " Client aborted file write" << endl; return EXIT_FAILURE; } case ERROR_ND: {// Nedefinovana chyba f->close_file(FILE_OUT); cout << current_time() << client_ip << " Client aborted file read (undefined error)" << endl; return EXIT_FAILURE; } } } break; } case UNKNOWN_OPCODE: {// Neznamy opcode err_msg = "Unknown request"; send_error(sockfd, client_addr, err_msg, ERROR_IL); clean_sources(f, sockfd); return EXIT_FAILURE; } }// konec switch(type) if(writing_data == true) {// Pokud probiha zapis, pouziva se vetsi buffer alokovany na halde recv_status = recv_packet(sockfd, client_addr, file_buf, file_buf_size); } else {// Pri cteni staci mensi velikost na zasobniku recv_status = recv_packet(sockfd, client_addr, buf, BUFSIZE); } if(recv_status == TIMEOUT_ELAPSED) {// Vyprsel cas cekani, probehne 3x pokus o znovu navazani komunikace tryouts++; if(tryouts == 3) {// Probehly tri pokusy o znovu navazani komunikace cout << current_time() << client_ip << " Timeout elapsed, operation aborted" << endl; clean_sources(f, sockfd); return 1; } else {// Probehne pokus cout << current_time() << client_ip << " Timeout elapsed, retrying..." << endl; } } else {// Byl normalne prijat paket numbytes = recv_status; tryouts = 0; // Vynuluje se pocitadlo neuspesnych pokusu } } }
PROCESS_THREAD(tftpd_process, ev, data) { static struct etimer t; static tftp_header *h; static int len, block, ack; static int tries; static int fd = -1; #if WITH_EXEC static char *elf_err; #endif PROCESS_BEGIN(); etimer_set(&t, CLOCK_CONF_SECOND*3); PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER); setup_server(); #if WITH_EXEC elfloader_init(); #endif print_local_addresses(); while(1) { /* connection from client */ RECV_PACKET(h); len = 0; init_config(); if(h->op == uip_htons(TFTP_RRQ)) { connect_back(); PRINTF("< rrq for %s\n", h->filename); len += strlen(h->filename)+1; if(strcmp("octet", h->filename+len)) { send_error(EUNDEF, "only octet mode supported"); goto close_connection; } len += strlen(h->filename+len)+1; /* skip mode */ parse_opts(h->options+len, uip_datalen()-len-2); if(config.to_ack & OACK_ERROR) { send_error(EOPTNEG, ""); goto close_connection; } fd = cfs_open(h->filename, CFS_READ); if(fd<0) { send_error(ENOTFOUND, ""); goto close_connection; } block = 0; ack = 0; tries = TFTP_MAXTRIES; PRINTF("starting transfer...\n"); for(;;) { if(send_oack()) len = config.blksize; /* XXX hack to prevent loop exit*/ else len = send_data(fd, block+1); if(len<0) { send_error(EUNDEF, "read failed"); goto close_file; } RECV_PACKET_TIMEOUT(h,t); if(ev == PROCESS_EVENT_TIMER) { PRINTF("ack timed out, tries left: %d\n", tries); if(--tries<=0) goto close_file; continue; } if(h->op != uip_htons(TFTP_ACK)) { send_error(EBADOP, ""); goto close_file; } config.to_ack = 0; tries = TFTP_MAXTRIES; ack = uip_htons(h->block_nr); if(ack == block+1) block++; if(len < config.blksize && ack == block) goto done; } } else if(h->op == uip_htons(TFTP_WRQ)) { connect_back(); PRINTF("< wrq for %s\n", h->filename); len += strlen(h->filename)+1; strncpy(config.filename, h->filename, sizeof(config.filename)-1); if(strcmp("octet", h->filename+strlen(h->filename)+1)) { send_error(EUNDEF, "only octet mode supported"); goto close_connection; } len += strlen(h->filename+len)+1; /* skip mode */ parse_opts(h->options+len, uip_datalen()-len-2); if(config.to_ack & OACK_ERROR) { send_error(EOPTNEG, ""); goto close_connection; } cfs_remove(h->filename); fd = cfs_open(h->filename, CFS_WRITE); if(fd<0) { send_error(EACCESS, ""); goto close_connection; } block = 0; ack = 0; tries = TFTP_MAXTRIES; PRINTF("starting transfer...\n"); if(!send_oack()) send_ack(block); for(;;) { RECV_PACKET_TIMEOUT(h,t); if(ev == PROCESS_EVENT_TIMER) { PRINTF("data timed out, tries left: %d\n", tries); if(--tries<=0) goto close_file; len = config.blksize; /* XXX hack to prevent loop exit*/ goto resend_ack; } if(h->op != uip_htons(TFTP_DATA)) { send_error(EBADOP, ""); goto close_file; } config.to_ack = 0; tries = TFTP_MAXTRIES; ack = uip_htons(h->block_nr); if(ack != block+1) continue; /* else */ block++; len = recv_data(fd, block); if(len<0) { send_error(EUNDEF, "write failed"); goto close_file; } #if WITH_EXEC if(len < config.blksize) { if(config.exec) { if(exec_file(config.filename, &elf_err) != 0) { send_error(EUNDEF, elf_err); goto close_file; } } } #endif resend_ack: if(!send_oack()) send_ack(block); if(len < config.blksize) goto done; } } done: PRINTF("done.\n"); close_file: if(fd>=0) cfs_close(fd); fd = -1; close_connection: if(client_conn) uip_udp_remove(client_conn); client_conn = 0; PRINTF("connection closed.\n"); } PROCESS_END(); }
/* * RRQ - send a file to the client */ void tftp_rrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strcpy(fnbuf, filename); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, RRQ); if (ecode == 0) { if (has_options) { int n; char lrecvbuffer[MAXPKTSIZE]; struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; send_oack(peer); n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n < 0) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Aborting: %s", rp_strerror(n)); return; } if (rp->th_opcode != ACK) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Expected ACK, got %s on OACK", packettype(rp->th_opcode)); return; } } } if (logging) tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, filename, errtomsg(ecode)); if (ecode) { /* * Avoid storms of naks to a RRQ broadcast for a relative * bootfile pathname from a diskless Sun. */ if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) exit(0); send_error(peer, ecode); exit(1); } tftp_xmitfile(peer, mode); }