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    
018    package org.apache.commons.net.tftp;
019    
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.InterruptedIOException;
023    import java.io.OutputStream;
024    import java.net.InetAddress;
025    import java.net.SocketException;
026    import java.net.UnknownHostException;
027    import org.apache.commons.net.io.FromNetASCIIOutputStream;
028    import org.apache.commons.net.io.ToNetASCIIInputStream;
029    
030    /***
031     * The TFTPClient class encapsulates all the aspects of the TFTP protocol
032     * necessary to receive and send files through TFTP.  It is derived from
033     * the {@link org.apache.commons.net.tftp.TFTP} because
034     * it is more convenient than using aggregation, and as a result exposes
035     * the same set of methods to allow you to deal with the TFTP protocol
036     * directly.  However, almost every user should only be concerend with the
037     * the {@link org.apache.commons.net.DatagramSocketClient#open  open() },
038     * {@link org.apache.commons.net.DatagramSocketClient#close  close() },
039     * {@link #sendFile  sendFile() }, and
040     * {@link #receiveFile  receiveFile() } methods.  Additionally, the
041     * {@link #setMaxTimeouts  setMaxTimeouts() } and
042     * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() }
043     *  methods may be of importance for performance
044     * tuning.
045     * <p>
046     * Details regarding the TFTP protocol and the format of TFTP packets can
047     * be found in RFC 783.  But the point of these classes is to keep you
048     * from having to worry about the internals.
049     * <p>
050     * <p>
051     * @author Daniel F. Savarese
052     * @see TFTP
053     * @see TFTPPacket
054     * @see TFTPPacketException
055     ***/
056    
057    public 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        /***
070         * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
071         * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
072         * and buffered operations disabled.
073         ***/
074        public TFTPClient()
075        {
076            __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
077        }
078    
079        /***
080         * Sets the maximum number of times a receive attempt is allowed to
081         * timeout during a receiveFile() or sendFile() operation before ending
082         * attempts to retry the receive and failing.
083         * The default is DEFAULT_MAX_TIMEOUTS.
084         * <p>
085         * @param numTimeouts  The maximum number of timeouts to allow.  Values
086         *        less than 1 should not be used, but if they are, they are
087         *        treated as 1.
088         ***/
089        public void setMaxTimeouts(int numTimeouts)
090        {
091            if (numTimeouts < 1)
092                __maxTimeouts = 1;
093            else
094                __maxTimeouts = numTimeouts;
095        }
096    
097        /***
098         * Returns the maximum number of times a receive attempt is allowed to
099         * timeout before ending attempts to retry the receive and failing.
100         * <p>
101         * @return The maximum number of timeouts allowed.
102         ***/
103        public int getMaxTimeouts()
104        {
105            return __maxTimeouts;
106        }
107    
108    
109        /***
110         * Requests a named file from a remote host, writes the
111         * file to an OutputStream, closes the connection, and returns the number
112         * of bytes read.  A local UDP socket must first be created by
113         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
114         * invoking this method.  This method will not close the OutputStream
115         * containing the file; you must close it after the method invocation.
116         * <p>
117         * @param filename  The name of the file to receive.
118         * @param mode   The TFTP mode of the transfer (one of the MODE constants).
119         * @param output The OutputStream to which the file should be written.
120         * @param host   The remote host serving the file.
121         * @param port   The port number of the remote TFTP server.
122         * @exception IOException If an I/O error occurs.  The nature of the
123         *            error will be reported in the message.
124         ***/
125        public int receiveFile(String filename, int mode, OutputStream output,
126                               InetAddress host, int port) throws IOException
127        {
128            int bytesRead, timeouts, lastBlock, block, hostPort, dataLength;
129            TFTPPacket sent, received = null;
130            TFTPErrorPacket error;
131            TFTPDataPacket data;
132            TFTPAckPacket ack = new TFTPAckPacket(host, port, 0);
133    
134            beginBufferedOps();
135    
136            dataLength = lastBlock = hostPort = bytesRead = 0;
137            block = 1;
138    
139            if (mode == TFTP.ASCII_MODE)
140                output = new FromNetASCIIOutputStream(output);
141    
142            sent =
143                new TFTPReadRequestPacket(host, port, filename, mode);
144    
145    _sendPacket:
146            do
147            {
148                bufferedSend(sent);
149    
150    _receivePacket:
151                while (true)
152                {
153                    timeouts = 0;
154                    while (timeouts < __maxTimeouts)
155                    {
156                        try
157                        {
158                            received = bufferedReceive();
159                            break;
160                        }
161                        catch (SocketException e)
162                        {
163                            if (++timeouts >= __maxTimeouts)
164                            {
165                                endBufferedOps();
166                                throw new IOException("Connection timed out.");
167                            }
168                            continue;
169                        }
170                        catch (InterruptedIOException e)
171                        {
172                            if (++timeouts >= __maxTimeouts)
173                            {
174                                endBufferedOps();
175                                throw new IOException("Connection timed out.");
176                            }
177                            continue;
178                        }
179                        catch (TFTPPacketException e)
180                        {
181                            endBufferedOps();
182                            throw new IOException("Bad packet: " + e.getMessage());
183                        }
184                    }
185    
186                    // The first time we receive we get the port number and
187                    // answering host address (for hosts with multiple IPs)
188                    if (lastBlock == 0)
189                    {
190                        hostPort = received.getPort();
191                        ack.setPort(hostPort);
192                        if(!host.equals(received.getAddress()))
193                        {
194                            host = received.getAddress();
195                            ack.setAddress(host);
196                            sent.setAddress(host);
197                        }
198                    }
199    
200                    // Comply with RFC 783 indication that an error acknowledgement
201                    // should be sent to originator if unexpected TID or host.
202                    if (host.equals(received.getAddress()) &&
203                            received.getPort() == hostPort)
204                    {
205    
206                        switch (received.getType())
207                        {
208                        case TFTPPacket.ERROR:
209                            error = (TFTPErrorPacket)received;
210                            endBufferedOps();
211                            throw new IOException("Error code " + error.getError() +
212                                                  " received: " + error.getMessage());
213                        case TFTPPacket.DATA:
214                            data = (TFTPDataPacket)received;
215                            dataLength = data.getDataLength();
216    
217                            lastBlock = data.getBlockNumber();
218    
219                            if (lastBlock == block)
220                            {
221                                try
222                                {
223                                    output.write(data.getData(), data.getDataOffset(),
224                                                 dataLength);
225                                }
226                                catch (IOException e)
227                                {
228                                    error = new TFTPErrorPacket(host, hostPort,
229                                                                TFTPErrorPacket.OUT_OF_SPACE,
230                                                                "File write failed.");
231                                    bufferedSend(error);
232                                    endBufferedOps();
233                                    throw e;
234                                }
235                                ++block;
236                                if (block > 65535)
237                                {
238                                    // wrap the block number
239                                    block = 0;
240                                }
241                                
242                                break _receivePacket;
243                            }
244                            else
245                            {
246                                discardPackets();
247    
248                                if (lastBlock == (block == 0 ? 65535 : (block - 1)))
249                                    continue _sendPacket;  // Resend last acknowledgement.
250    
251                                continue _receivePacket; // Start fetching packets again.
252                            }
253                            //break;
254    
255                        default:
256                            endBufferedOps();
257                            throw new IOException("Received unexpected packet type.");
258                        }
259                    }
260                    else
261                    {
262                        error = new TFTPErrorPacket(received.getAddress(),
263                                                    received.getPort(),
264                                                    TFTPErrorPacket.UNKNOWN_TID,
265                                                    "Unexpected host or port.");
266                        bufferedSend(error);
267                        continue _sendPacket;
268                    }
269    
270                    // We should never get here, but this is a safety to avoid
271                    // infinite loop.  If only Java had the goto statement.
272                    //break;
273                }
274    
275                ack.setBlockNumber(lastBlock);
276                sent = ack;
277                bytesRead += dataLength;
278            } // First data packet less than 512 bytes signals end of stream.
279    
280            while (dataLength == TFTPPacket.SEGMENT_SIZE);
281    
282            bufferedSend(sent);
283            endBufferedOps();
284    
285            return bytesRead;
286        }
287    
288    
289        /***
290         * Requests a named file from a remote host, writes the
291         * file to an OutputStream, closes the connection, and returns the number
292         * of bytes read.  A local UDP socket must first be created by
293         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
294         * invoking this method.  This method will not close the OutputStream
295         * containing the file; you must close it after the method invocation.
296         * <p>
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 hostname The name of the remote host serving the file.
301         * @param port     The port number of the remote TFTP server.
302         * @exception IOException If an I/O error occurs.  The nature of the
303         *            error will be reported in the message.
304         * @exception UnknownHostException  If the hostname cannot be resolved.
305         ***/
306        public int receiveFile(String filename, int mode, OutputStream output,
307                               String hostname, int port)
308        throws UnknownHostException, IOException
309        {
310            return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
311                               port);
312        }
313    
314    
315        /***
316         * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
317         *
318         * @param filename The name of the file to receive.
319         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
320         * @param output   The OutputStream to which the file should be written.
321         * @param host     The remote host serving the file.
322         * @exception IOException If an I/O error occurs.  The nature of the
323         *            error will be reported in the message.
324         ***/
325        public int receiveFile(String filename, int mode, OutputStream output,
326                               InetAddress host)
327        throws IOException
328        {
329            return receiveFile(filename, mode, output, host, DEFAULT_PORT);
330        }
331    
332        /***
333         * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
334         *
335         * @param filename The name of the file to receive.
336         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
337         * @param output   The OutputStream to which the file should be written.
338         * @param hostname The name of the remote host serving the file.
339         * @exception IOException If an I/O error occurs.  The nature of the
340         *            error will be reported in the message.
341         * @exception UnknownHostException  If the hostname cannot be resolved.
342         ***/
343        public int receiveFile(String filename, int mode, OutputStream output,
344                               String hostname)
345        throws UnknownHostException, IOException
346        {
347            return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
348                               DEFAULT_PORT);
349        }
350    
351    
352        /***
353         * Requests to send a file to a remote host, reads the file from an
354         * InputStream, sends the file to the remote host, and closes the
355         * connection.  A local UDP socket must first be created by
356         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
357         * invoking this method.  This method will not close the InputStream
358         * containing the file; you must close it after the method invocation.
359         * <p>
360         * @param filename The name the remote server should use when creating
361         *        the file on its file system.
362         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
363         * @param host     The remote host receiving the file.
364         * @param port     The port number of the remote TFTP server.
365         * @exception IOException If an I/O error occurs.  The nature of the
366         *            error will be reported in the message.
367         ***/
368        public void sendFile(String filename, int mode, InputStream input,
369                             InetAddress host, int port) throws IOException
370        {
371            int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket;
372            TFTPPacket sent, received = null;
373            TFTPErrorPacket error;
374            TFTPDataPacket data =
375                new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
376            TFTPAckPacket ack;
377    
378            boolean justStarted = true;
379            
380            beginBufferedOps();
381    
382            dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0;
383            block = 0;
384            boolean lastAckWait = false;
385    
386            if (mode == TFTP.ASCII_MODE)
387                input = new ToNetASCIIInputStream(input);
388    
389            sent =
390                new TFTPWriteRequestPacket(host, port, filename, mode);
391    
392    _sendPacket:
393            do
394            {
395                // first time: block is 0, lastBlock is 0, send a request packet.
396                // subsequent: block is integer starting at 1, send data packet.
397                bufferedSend(sent);
398                
399                // this is trying to receive an ACK
400    _receivePacket:
401                while (true)
402                {
403                    
404    
405                    timeouts = 0;
406                    while (timeouts < __maxTimeouts)
407                    {
408                        try
409                        {
410                            received = bufferedReceive();
411                            break;
412                        }
413                        catch (SocketException e)
414                        {
415                            if (++timeouts >= __maxTimeouts)
416                            {
417                                endBufferedOps();
418                                throw new IOException("Connection timed out.");
419                            }
420                            continue;
421                        }
422                        catch (InterruptedIOException e)
423                        {
424                            if (++timeouts >= __maxTimeouts)
425                            {
426                                endBufferedOps();
427                                throw new IOException("Connection timed out.");
428                            }
429                            continue;
430                        }
431                        catch (TFTPPacketException e)
432                        {
433                            endBufferedOps();
434                            throw new IOException("Bad packet: " + e.getMessage());
435                        }
436                    } // end of while loop over tries to receive
437    
438                    // The first time we receive we get the port number and
439            // answering host address (for hosts with multiple IPs)
440                    if (justStarted)
441                    {
442                        justStarted = false;
443                        hostPort = received.getPort();
444                        data.setPort(hostPort);
445                        if(!host.equals(received.getAddress()))
446                        {
447                            host = received.getAddress();
448                            data.setAddress(host);
449                            sent.setAddress(host);
450                        }
451                    }
452    
453                    // Comply with RFC 783 indication that an error acknowledgement
454                    // should be sent to originator if unexpected TID or host.
455                    if (host.equals(received.getAddress()) &&
456                            received.getPort() == hostPort)
457                    {
458    
459                        switch (received.getType())
460                        {
461                        case TFTPPacket.ERROR:
462                            error = (TFTPErrorPacket)received;
463                            endBufferedOps();
464                            throw new IOException("Error code " + error.getError() +
465                                                  " received: " + error.getMessage());
466                        case TFTPPacket.ACKNOWLEDGEMENT:
467                            ack = (TFTPAckPacket)received;
468    
469                            lastBlock = ack.getBlockNumber();
470    
471                            if (lastBlock == block)
472                            {
473                                ++block;
474                                if (block > 65535)
475                                {
476                                    // wrap the block number
477                                    block = 0;
478                                }
479                                if (lastAckWait) {
480                                    
481                                  break _sendPacket;
482                                }
483                                else {
484                                  break _receivePacket;
485                                }
486                            }
487                            else
488                            {
489                                discardPackets();
490    
491                                if (lastBlock == (block == 0 ? 65535 : (block - 1)))
492                                    continue _sendPacket;  // Resend last acknowledgement.
493    
494                                continue _receivePacket; // Start fetching packets again.
495                            }
496                            //break;
497    
498                        default:
499                            endBufferedOps();
500                            throw new IOException("Received unexpected packet type.");
501                        }
502                    }
503                    else
504                    {
505                        error = new TFTPErrorPacket(received.getAddress(),
506                                                    received.getPort(),
507                                                    TFTPErrorPacket.UNKNOWN_TID,
508                                                    "Unexpected host or port.");
509                        bufferedSend(error);
510                        continue _sendPacket;
511                    }
512    
513                    // We should never get here, but this is a safety to avoid
514                    // infinite loop.  If only Java had the goto statement.
515                    //break;
516                }
517    
518                // OK, we have just gotten ACK about the last data we sent. Make another
519                // and send it            
520    
521                dataLength = TFTPPacket.SEGMENT_SIZE;
522                offset = 4;
523                totalThisPacket = 0;
524                while (dataLength > 0 &&
525                        (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0)
526                {
527                    offset += bytesRead;
528                    dataLength -= bytesRead;
529                    totalThisPacket += bytesRead;
530                }
531    
532                if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) {
533                    /* this will be our last packet -- send, wait for ack, stop */
534                    lastAckWait = true;
535                }
536                data.setBlockNumber(block);
537                data.setData(_sendBuffer, 4, totalThisPacket);
538                sent = data;
539            }
540            while ( totalThisPacket > 0 || lastAckWait );
541            // Note: this was looping while dataLength == 0 || lastAckWait,
542            // which was discarding the last packet if it was not full size
543            // Should send the packet. 
544            
545            endBufferedOps();
546        }
547    
548    
549        /***
550         * Requests to send a file to a remote host, reads the file from an
551         * InputStream, sends the file to the remote host, and closes the
552         * connection.  A local UDP socket must first be created by
553         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
554         * invoking this method.  This method will not close the InputStream
555         * containing the file; you must close it after the method invocation.
556         * <p>
557         * @param filename The name the remote server should use when creating
558         *        the file on its file system.
559         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
560         * @param hostname The name of the remote host receiving the file.
561         * @param port     The port number of the remote TFTP server.
562         * @exception IOException If an I/O error occurs.  The nature of the
563         *            error will be reported in the message.
564         * @exception UnknownHostException  If the hostname cannot be resolved.
565         ***/
566        public void sendFile(String filename, int mode, InputStream input,
567                             String hostname, int port)
568        throws UnknownHostException, IOException
569        {
570            sendFile(filename, mode, input, InetAddress.getByName(hostname), port);
571        }
572    
573    
574        /***
575         * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT).
576         *
577         * @param filename The name the remote server should use when creating
578         *        the file on its file system.
579         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
580         * @param host     The name of the remote host receiving the file.
581         * @exception IOException If an I/O error occurs.  The nature of the
582         *            error will be reported in the message.
583         * @exception UnknownHostException  If the hostname cannot be resolved.
584         ***/
585        public void sendFile(String filename, int mode, InputStream input,
586                             InetAddress host)
587        throws IOException
588        {
589            sendFile(filename, mode, input, host, DEFAULT_PORT);
590        }
591    
592        /***
593         * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT).
594         *
595         * @param filename The name the remote server should use when creating
596         *        the file on its file system.
597         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
598         * @param hostname The name of the remote host receiving the file.
599         * @exception IOException If an I/O error occurs.  The nature of the
600         *            error will be reported in the message.
601         * @exception UnknownHostException  If the hostname cannot be resolved.
602         ***/
603        public void sendFile(String filename, int mode, InputStream input,
604                             String hostname)
605        throws UnknownHostException, IOException
606        {
607            sendFile(filename, mode, input, InetAddress.getByName(hostname),
608                     DEFAULT_PORT);
609        }
610    }