001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.tftp; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.InterruptedIOException; 023import java.io.OutputStream; 024import java.net.InetAddress; 025import java.net.SocketException; 026import java.net.UnknownHostException; 027 028import org.apache.commons.net.io.FromNetASCIIOutputStream; 029import org.apache.commons.net.io.ToNetASCIIInputStream; 030 031/** 032 * The TFTPClient class encapsulates all the aspects of the TFTP protocol 033 * necessary to receive and send files through TFTP. It is derived from 034 * the {@link org.apache.commons.net.tftp.TFTP} because 035 * it is more convenient than using aggregation, and as a result exposes 036 * the same set of methods to allow you to deal with the TFTP protocol 037 * directly. However, almost every user should only be concerend with the 038 * the {@link org.apache.commons.net.DatagramSocketClient#open open() }, 039 * {@link org.apache.commons.net.DatagramSocketClient#close close() }, 040 * {@link #sendFile sendFile() }, and 041 * {@link #receiveFile receiveFile() } methods. Additionally, the 042 * {@link #setMaxTimeouts setMaxTimeouts() } and 043 * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } 044 * methods may be of importance for performance 045 * tuning. 046 * <p> 047 * Details regarding the TFTP protocol and the format of TFTP packets can 048 * be found in RFC 783. But the point of these classes is to keep you 049 * from having to worry about the internals. 050 * 051 * 052 * @see TFTP 053 * @see TFTPPacket 054 * @see TFTPPacketException 055 */ 056 057public class TFTPClient extends TFTP 058{ 059 /** 060 * The default number of times a receive attempt is allowed to timeout 061 * before ending attempts to retry the receive and failing. The default 062 * is 5 timeouts. 063 */ 064 public static final int DEFAULT_MAX_TIMEOUTS = 5; 065 066 /** The maximum number of timeouts allowed before failing. */ 067 private int maxTimeouts; 068 069 /** The number of bytes received in the ongoing download. */ 070 private long totalBytesReceived; 071 072 /** The number of bytes sent in the ongoing upload. */ 073 private long totalBytesSent; 074 075 /** 076 * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, 077 * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, 078 * and buffered operations disabled. 079 */ 080 public TFTPClient() 081 { 082 maxTimeouts = DEFAULT_MAX_TIMEOUTS; 083 } 084 085 /** 086 * Sets the maximum number of times a receive attempt is allowed to 087 * timeout during a receiveFile() or sendFile() operation before ending 088 * attempts to retry the receive and failing. 089 * The default is DEFAULT_MAX_TIMEOUTS. 090 * 091 * @param numTimeouts The maximum number of timeouts to allow. Values 092 * less than 1 should not be used, but if they are, they are 093 * treated as 1. 094 */ 095 public void setMaxTimeouts(final int numTimeouts) 096 { 097 if (numTimeouts < 1) { 098 maxTimeouts = 1; 099 } else { 100 maxTimeouts = numTimeouts; 101 } 102 } 103 104 /** 105 * Returns the maximum number of times a receive attempt is allowed to 106 * timeout before ending attempts to retry the receive and failing. 107 * 108 * @return The maximum number of timeouts allowed. 109 */ 110 public int getMaxTimeouts() 111 { 112 return maxTimeouts; 113 } 114 115 116 /** 117 * @return The number of bytes received in the ongoing download 118 */ 119 public long getTotalBytesReceived() { 120 return totalBytesReceived; 121 } 122 123 /** 124 * @return The number of bytes sent in the ongoing download 125 */ 126 public long getTotalBytesSent() { 127 return totalBytesSent; 128 } 129 130 /** 131 * Requests a named file from a remote host, writes the 132 * file to an OutputStream, closes the connection, and returns the number 133 * of bytes read. A local UDP socket must first be created by 134 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 135 * invoking this method. This method will not close the OutputStream 136 * containing the file; you must close it after the method invocation. 137 * 138 * @param fileName The name of the file to receive. 139 * @param mode The TFTP mode of the transfer (one of the MODE constants). 140 * @param output The OutputStream to which the file should be written. 141 * @param host The remote host serving the file. 142 * @param port The port number of the remote TFTP server. 143 * @return number of bytes read 144 * @throws IOException If an I/O error occurs. The nature of the 145 * error will be reported in the message. 146 */ 147 public int receiveFile(final String fileName, final int mode, OutputStream output, 148 InetAddress host, final int port) throws IOException 149 { 150 int bytesRead = 0; 151 int lastBlock = 0; 152 int block = 1; 153 int hostPort = 0; 154 int dataLength = 0; 155 156 totalBytesReceived = 0; 157 158 if (mode == TFTP.ASCII_MODE) { 159 output = new FromNetASCIIOutputStream(output); 160 } 161 162 TFTPPacket sent = new TFTPReadRequestPacket(host, port, fileName, mode); 163 final TFTPAckPacket ack = new TFTPAckPacket(host, port, 0); 164 165 beginBufferedOps(); 166 167 boolean justStarted = true; 168 try { 169 do { // while more data to fetch 170 bufferedSend(sent); // start the fetch/send an ack 171 boolean wantReply = true; 172 int timeouts = 0; 173 do { // until successful response 174 try { 175 final TFTPPacket received = bufferedReceive(); 176 // The first time we receive we get the port number and 177 // answering host address (for hosts with multiple IPs) 178 final int recdPort = received.getPort(); 179 final InetAddress recdAddress = received.getAddress(); 180 if (justStarted) { 181 justStarted = false; 182 if (recdPort == port) { // must not use the control port here 183 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, 184 recdPort, TFTPErrorPacket.UNKNOWN_TID, 185 "INCORRECT SOURCE PORT"); 186 bufferedSend(error); 187 throw new IOException("Incorrect source port ("+recdPort+") in request reply."); 188 } 189 hostPort = recdPort; 190 ack.setPort(hostPort); 191 if(!host.equals(recdAddress)) 192 { 193 host = recdAddress; 194 ack.setAddress(host); 195 sent.setAddress(host); 196 } 197 } 198 // Comply with RFC 783 indication that an error acknowledgment 199 // should be sent to originator if unexpected TID or host. 200 if (host.equals(recdAddress) && recdPort == hostPort) { 201 switch (received.getType()) { 202 203 case TFTPPacket.ERROR: 204 TFTPErrorPacket error = (TFTPErrorPacket)received; 205 throw new IOException("Error code " + error.getError() + 206 " received: " + error.getMessage()); 207 case TFTPPacket.DATA: 208 final TFTPDataPacket data = (TFTPDataPacket)received; 209 dataLength = data.getDataLength(); 210 lastBlock = data.getBlockNumber(); 211 212 if (lastBlock == block) { // is the next block number? 213 try { 214 output.write(data.getData(), data.getDataOffset(), dataLength); 215 } catch (final IOException e) { 216 error = new TFTPErrorPacket(host, hostPort, 217 TFTPErrorPacket.OUT_OF_SPACE, 218 "File write failed."); 219 bufferedSend(error); 220 throw e; 221 } 222 ++block; 223 if (block > 65535) { 224 // wrap the block number 225 block = 0; 226 } 227 wantReply = false; // got the next block, drop out to ack it 228 } else { // unexpected block number 229 discardPackets(); 230 if (lastBlock == (block == 0 ? 65535 : block - 1)) { 231 wantReply = false; // Resend last acknowledgemen 232 } 233 } 234 break; 235 236 default: 237 throw new IOException("Received unexpected packet type (" + received.getType() + ")"); 238 } 239 } else { // incorrect host or TID 240 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, 241 TFTPErrorPacket.UNKNOWN_TID, 242 "Unexpected host or port."); 243 bufferedSend(error); 244 } 245 } catch (final SocketException | InterruptedIOException e) { 246 if (++timeouts >= maxTimeouts) { 247 throw new IOException("Connection timed out."); 248 } 249 } catch (final TFTPPacketException e) { 250 throw new IOException("Bad packet: " + e.getMessage()); 251 } 252 } while(wantReply); // waiting for response 253 254 ack.setBlockNumber(lastBlock); 255 sent = ack; 256 bytesRead += dataLength; 257 totalBytesReceived += dataLength; 258 } while (dataLength == TFTPPacket.SEGMENT_SIZE); // not eof 259 bufferedSend(sent); // send the final ack 260 } finally { 261 endBufferedOps(); 262 } 263 return bytesRead; 264 } 265 266 267 /** 268 * Requests a named file from a remote host, writes the 269 * file to an OutputStream, closes the connection, and returns the number 270 * of bytes read. A local UDP socket must first be created by 271 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 272 * invoking this method. This method will not close the OutputStream 273 * containing the file; you must close it after the method invocation. 274 * 275 * @param fileName The name of the file to receive. 276 * @param mode The TFTP mode of the transfer (one of the MODE constants). 277 * @param output The OutputStream to which the file should be written. 278 * @param hostname The name of the remote host serving the file. 279 * @param port The port number of the remote TFTP server. 280 * @return number of bytes read 281 * @throws IOException If an I/O error occurs. The nature of the 282 * error will be reported in the message. 283 * @throws UnknownHostException If the hostname cannot be resolved. 284 */ 285 public int receiveFile(final String fileName, final int mode, final OutputStream output, 286 final String hostname, final int port) 287 throws UnknownHostException, IOException 288 { 289 return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), 290 port); 291 } 292 293 294 /** 295 * Same as calling receiveFile(fileName, mode, output, host, TFTP.DEFAULT_PORT). 296 * 297 * @param fileName The name of the file to receive. 298 * @param mode The TFTP mode of the transfer (one of the MODE constants). 299 * @param output The OutputStream to which the file should be written. 300 * @param host The remote host serving the file. 301 * @return number of bytes read 302 * @throws IOException If an I/O error occurs. The nature of the 303 * error will be reported in the message. 304 */ 305 public int receiveFile(final String fileName, final int mode, final OutputStream output, 306 final InetAddress host) 307 throws IOException 308 { 309 return receiveFile(fileName, mode, output, host, DEFAULT_PORT); 310 } 311 312 /** 313 * Same as calling receiveFile(fileName, mode, output, hostname, TFTP.DEFAULT_PORT). 314 * 315 * @param fileName The name of the file to receive. 316 * @param mode The TFTP mode of the transfer (one of the MODE constants). 317 * @param output The OutputStream to which the file should be written. 318 * @param hostname The name of the remote host serving the file. 319 * @return number of bytes read 320 * @throws IOException If an I/O error occurs. The nature of the 321 * error will be reported in the message. 322 * @throws UnknownHostException If the hostname cannot be resolved. 323 */ 324 public int receiveFile(final String fileName, final int mode, final OutputStream output, 325 final String hostname) 326 throws UnknownHostException, IOException 327 { 328 return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), 329 DEFAULT_PORT); 330 } 331 332 333 /** 334 * Requests to send a file to a remote host, reads the file from an 335 * InputStream, sends the file to the remote host, and closes the 336 * connection. A local UDP socket must first be created by 337 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 338 * invoking this method. This method will not close the InputStream 339 * containing the file; you must close it after the method invocation. 340 * 341 * @param fileName The name the remote server should use when creating 342 * the file on its file system. 343 * @param mode The TFTP mode of the transfer (one of the MODE constants). 344 * @param input the input stream containing the data to be sent 345 * @param host The remote host receiving the file. 346 * @param port The port number of the remote TFTP server. 347 * @throws IOException If an I/O error occurs. The nature of the 348 * error will be reported in the message. 349 */ 350 public void sendFile(final String fileName, final int mode, InputStream input, 351 InetAddress host, final int port) throws IOException 352 { 353 int block = 0; 354 int hostPort = 0; 355 boolean justStarted = true; 356 boolean lastAckWait = false; 357 358 totalBytesSent = 0L; 359 360 if (mode == TFTP.ASCII_MODE) { 361 input = new ToNetASCIIInputStream(input); 362 } 363 364 TFTPPacket sent = new TFTPWriteRequestPacket(host, port, fileName, mode); 365 final TFTPDataPacket data = new TFTPDataPacket(host, port, 0, sendBuffer, 4, 0); 366 367 beginBufferedOps(); 368 369 try { 370 do { // until eof 371 // first time: block is 0, lastBlock is 0, send a request packet. 372 // subsequent: block is integer starting at 1, send data packet. 373 bufferedSend(sent); 374 boolean wantReply = true; 375 int timeouts = 0; 376 do { 377 try { 378 final TFTPPacket received = bufferedReceive(); 379 final InetAddress recdAddress = received.getAddress(); 380 final int recdPort = received.getPort(); 381 // The first time we receive we get the port number and 382 // answering host address (for hosts with multiple IPs) 383 if (justStarted) { 384 justStarted = false; 385 if (recdPort == port) { // must not use the control port here 386 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, 387 recdPort, TFTPErrorPacket.UNKNOWN_TID, 388 "INCORRECT SOURCE PORT"); 389 bufferedSend(error); 390 throw new IOException("Incorrect source port ("+recdPort+") in request reply."); 391 } 392 hostPort = recdPort; 393 data.setPort(hostPort); 394 if (!host.equals(recdAddress)) { 395 host = recdAddress; 396 data.setAddress(host); 397 sent.setAddress(host); 398 } 399 } 400 // Comply with RFC 783 indication that an error acknowledgment 401 // should be sent to originator if unexpected TID or host. 402 if (host.equals(recdAddress) && recdPort == hostPort) { 403 404 switch (received.getType()) { 405 case TFTPPacket.ERROR: 406 final TFTPErrorPacket error = (TFTPErrorPacket)received; 407 throw new IOException("Error code " + error.getError() + 408 " received: " + error.getMessage()); 409 case TFTPPacket.ACKNOWLEDGEMENT: 410 411 final int lastBlock = ((TFTPAckPacket)received).getBlockNumber(); 412 413 if (lastBlock == block) { 414 ++block; 415 if (block > 65535) { 416 // wrap the block number 417 block = 0; 418 } 419 wantReply = false; // got the ack we want 420 } else { 421 discardPackets(); 422 } 423 break; 424 default: 425 throw new IOException("Received unexpected packet type."); 426 } 427 } else { // wrong host or TID; send error 428 final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, 429 recdPort, 430 TFTPErrorPacket.UNKNOWN_TID, 431 "Unexpected host or port."); 432 bufferedSend(error); 433 } 434 } catch (final SocketException | InterruptedIOException e) { 435 if (++timeouts >= maxTimeouts) { 436 throw new IOException("Connection timed out."); 437 } 438 } catch (final TFTPPacketException e) { 439 throw new IOException("Bad packet: " + e.getMessage()); 440 } 441 // retry until a good ack 442 } while(wantReply); 443 444 if (lastAckWait) { 445 break; // we were waiting for this; now all done 446 } 447 448 int dataLength = TFTPPacket.SEGMENT_SIZE; 449 int offset = 4; 450 int totalThisPacket = 0; 451 int bytesRead = 0; 452 while (dataLength > 0 && 453 (bytesRead = input.read(sendBuffer, offset, dataLength)) > 0) { 454 offset += bytesRead; 455 dataLength -= bytesRead; 456 totalThisPacket += bytesRead; 457 } 458 if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) { 459 /* this will be our last packet -- send, wait for ack, stop */ 460 lastAckWait = true; 461 } 462 data.setBlockNumber(block); 463 data.setData(sendBuffer, 4, totalThisPacket); 464 sent = data; 465 totalBytesSent += totalThisPacket; 466 } while (true); // loops until after lastAckWait is set 467 } finally { 468 endBufferedOps(); 469 } 470 } 471 472 473 /** 474 * Requests to send a file to a remote host, reads the file from an 475 * InputStream, sends the file to the remote host, and closes the 476 * connection. A local UDP socket must first be created by 477 * {@link org.apache.commons.net.DatagramSocketClient#open open()} before 478 * invoking this method. This method will not close the InputStream 479 * containing the file; you must close it after the method invocation. 480 * 481 * @param fileName The name the remote server should use when creating 482 * the file on its file system. 483 * @param mode The TFTP mode of the transfer (one of the MODE constants). 484 * @param input the input stream containing the data to be sent 485 * @param hostname The name of the remote host receiving the file. 486 * @param port The port number of the remote TFTP server. 487 * @throws IOException If an I/O error occurs. The nature of the 488 * error will be reported in the message. 489 * @throws UnknownHostException If the hostname cannot be resolved. 490 */ 491 public void sendFile(final String fileName, final int mode, final InputStream input, 492 final String hostname, final int port) 493 throws UnknownHostException, IOException 494 { 495 sendFile(fileName, mode, input, InetAddress.getByName(hostname), port); 496 } 497 498 499 /** 500 * Same as calling sendFile(fileName, mode, input, host, TFTP.DEFAULT_PORT). 501 * 502 * @param fileName The name the remote server should use when creating 503 * the file on its file system. 504 * @param mode The TFTP mode of the transfer (one of the MODE constants). 505 * @param input the input stream containing the data to be sent 506 * @param host The name of the remote host receiving the file. 507 * @throws IOException If an I/O error occurs. The nature of the 508 * error will be reported in the message. 509 * @throws UnknownHostException If the hostname cannot be resolved. 510 */ 511 public void sendFile(final String fileName, final int mode, final InputStream input, 512 final InetAddress host) 513 throws IOException 514 { 515 sendFile(fileName, mode, input, host, DEFAULT_PORT); 516 } 517 518 /** 519 * Same as calling sendFile(fileName, mode, input, hostname, TFTP.DEFAULT_PORT). 520 * 521 * @param fileName The name the remote server should use when creating 522 * the file on its file system. 523 * @param mode The TFTP mode of the transfer (one of the MODE constants). 524 * @param input the input stream containing the data to be sent 525 * @param hostname The name of the remote host receiving the file. 526 * @throws IOException If an I/O error occurs. The nature of the 527 * error will be reported in the message. 528 * @throws UnknownHostException If the hostname cannot be resolved. 529 */ 530 public void sendFile(final String fileName, final int mode, final InputStream input, 531 final String hostname) 532 throws UnknownHostException, IOException 533 { 534 sendFile(fileName, mode, input, InetAddress.getByName(hostname), 535 DEFAULT_PORT); 536 } 537}