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     * @see TFTP
052     * @see TFTPPacket
053     * @see TFTPPacketException
054     ***/
055    
056    public class TFTPClient extends TFTP
057    {
058        /***
059         * The default number of times a receive attempt is allowed to timeout
060         * before ending attempts to retry the receive and failing.  The default
061         * is 5 timeouts.
062         ***/
063        public static final int DEFAULT_MAX_TIMEOUTS = 5;
064    
065        /*** The maximum number of timeouts allowed before failing. ***/
066        private int __maxTimeouts;
067    
068        /***
069         * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT,
070         * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket,
071         * and buffered operations disabled.
072         ***/
073        public TFTPClient()
074        {
075            __maxTimeouts = DEFAULT_MAX_TIMEOUTS;
076        }
077    
078        /***
079         * Sets the maximum number of times a receive attempt is allowed to
080         * timeout during a receiveFile() or sendFile() operation before ending
081         * attempts to retry the receive and failing.
082         * The default is DEFAULT_MAX_TIMEOUTS.
083         * <p>
084         * @param numTimeouts  The maximum number of timeouts to allow.  Values
085         *        less than 1 should not be used, but if they are, they are
086         *        treated as 1.
087         ***/
088        public void setMaxTimeouts(int numTimeouts)
089        {
090            if (numTimeouts < 1) {
091                __maxTimeouts = 1;
092            } else {
093                __maxTimeouts = numTimeouts;
094            }
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    
143            sent =
144                new TFTPReadRequestPacket(host, port, filename, mode);
145    
146    _sendPacket:
147            do
148            {
149                bufferedSend(sent);
150    
151    _receivePacket:
152                while (true)
153                {
154                    timeouts = 0;
155                    do {
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 _sendPacket;
169                        }
170                        catch (InterruptedIOException e)
171                        {
172                            if (++timeouts >= __maxTimeouts)
173                            {
174                                endBufferedOps();
175                                throw new IOException("Connection timed out.");
176                            }
177                            continue _sendPacket;
178                        }
179                        catch (TFTPPacketException e)
180                        {
181                            endBufferedOps();
182                            throw new IOException("Bad packet: " + e.getMessage());
183                        }
184                    } while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
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    
252                                continue _receivePacket; // Start fetching packets again.
253                            }
254                            //break;
255    
256                        default:
257                            endBufferedOps();
258                            throw new IOException("Received unexpected packet type.");
259                        }
260                    }
261                    else
262                    {
263                        error = new TFTPErrorPacket(received.getAddress(),
264                                                    received.getPort(),
265                                                    TFTPErrorPacket.UNKNOWN_TID,
266                                                    "Unexpected host or port.");
267                        bufferedSend(error);
268                        continue _sendPacket;
269                    }
270    
271                    // We should never get here, but this is a safety to avoid
272                    // infinite loop.  If only Java had the goto statement.
273                    //break;
274                }
275    
276                ack.setBlockNumber(lastBlock);
277                sent = ack;
278                bytesRead += dataLength;
279            } // First data packet less than 512 bytes signals end of stream.
280    
281            while (dataLength == TFTPPacket.SEGMENT_SIZE);
282    
283            bufferedSend(sent);
284            endBufferedOps();
285    
286            return bytesRead;
287        }
288    
289    
290        /***
291         * Requests a named file from a remote host, writes the
292         * file to an OutputStream, closes the connection, and returns the number
293         * of bytes read.  A local UDP socket must first be created by
294         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
295         * invoking this method.  This method will not close the OutputStream
296         * containing the file; you must close it after the method invocation.
297         * <p>
298         * @param filename The name of the file to receive.
299         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
300         * @param output   The OutputStream to which the file should be written.
301         * @param hostname The name of the remote host serving the file.
302         * @param port     The port number of the remote TFTP server.
303         * @exception IOException If an I/O error occurs.  The nature of the
304         *            error will be reported in the message.
305         * @exception UnknownHostException  If the hostname cannot be resolved.
306         ***/
307        public int receiveFile(String filename, int mode, OutputStream output,
308                               String hostname, int port)
309        throws UnknownHostException, IOException
310        {
311            return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
312                               port);
313        }
314    
315    
316        /***
317         * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT).
318         *
319         * @param filename The name of the file to receive.
320         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
321         * @param output   The OutputStream to which the file should be written.
322         * @param host     The remote host serving the file.
323         * @exception IOException If an I/O error occurs.  The nature of the
324         *            error will be reported in the message.
325         ***/
326        public int receiveFile(String filename, int mode, OutputStream output,
327                               InetAddress host)
328        throws IOException
329        {
330            return receiveFile(filename, mode, output, host, DEFAULT_PORT);
331        }
332    
333        /***
334         * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT).
335         *
336         * @param filename The name of the file to receive.
337         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
338         * @param output   The OutputStream to which the file should be written.
339         * @param hostname The name of the remote host serving the file.
340         * @exception IOException If an I/O error occurs.  The nature of the
341         *            error will be reported in the message.
342         * @exception UnknownHostException  If the hostname cannot be resolved.
343         ***/
344        public int receiveFile(String filename, int mode, OutputStream output,
345                               String hostname)
346        throws UnknownHostException, IOException
347        {
348            return receiveFile(filename, mode, output, InetAddress.getByName(hostname),
349                               DEFAULT_PORT);
350        }
351    
352    
353        /***
354         * Requests to send a file to a remote host, reads the file from an
355         * InputStream, sends the file to the remote host, and closes the
356         * connection.  A local UDP socket must first be created by
357         * {@link org.apache.commons.net.DatagramSocketClient#open open()} before
358         * invoking this method.  This method will not close the InputStream
359         * containing the file; you must close it after the method invocation.
360         * <p>
361         * @param filename The name the remote server should use when creating
362         *        the file on its file system.
363         * @param mode     The TFTP mode of the transfer (one of the MODE constants).
364         * @param host     The remote host receiving the file.
365         * @param port     The port number of the remote TFTP server.
366         * @exception IOException If an I/O error occurs.  The nature of the
367         *            error will be reported in the message.
368         ***/
369        public void sendFile(String filename, int mode, InputStream input,
370                             InetAddress host, int port) throws IOException
371        {
372            int bytesRead, timeouts, lastBlock, block, hostPort, dataLength, offset, totalThisPacket;
373            TFTPPacket sent, received = null;
374            TFTPErrorPacket error;
375            TFTPDataPacket data =
376                new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0);
377            TFTPAckPacket ack;
378    
379            boolean justStarted = true;
380    
381            beginBufferedOps();
382    
383            dataLength = lastBlock = hostPort = bytesRead = totalThisPacket = 0;
384            block = 0;
385            boolean lastAckWait = false;
386    
387            if (mode == TFTP.ASCII_MODE) {
388                input = new ToNetASCIIInputStream(input);
389            }
390    
391            sent =
392                new TFTPWriteRequestPacket(host, port, filename, mode);
393    
394    _sendPacket:
395            do
396            {
397                // first time: block is 0, lastBlock is 0, send a request packet.
398                // subsequent: block is integer starting at 1, send data packet.
399                bufferedSend(sent);
400    
401                // this is trying to receive an ACK
402    _receivePacket:
403                while (true)
404                {
405    
406    
407                    timeouts = 0;
408                    do {
409                        try
410                        {
411                            received = bufferedReceive();
412                            break;
413                        }
414                        catch (SocketException e)
415                        {
416                            if (++timeouts >= __maxTimeouts)
417                            {
418                                endBufferedOps();
419                                throw new IOException("Connection timed out.");
420                            }
421                            continue _sendPacket;
422                        }
423                        catch (InterruptedIOException e)
424                        {
425                            if (++timeouts >= __maxTimeouts)
426                            {
427                                endBufferedOps();
428                                throw new IOException("Connection timed out.");
429                            }
430                            continue _sendPacket;
431                        }
432                        catch (TFTPPacketException e)
433                        {
434                            endBufferedOps();
435                            throw new IOException("Bad packet: " + e.getMessage());
436                        }
437                    } // end of while loop over tries to receive
438                    while (timeouts < __maxTimeouts); // __maxTimeouts >=1 so will always do loop at least once
439    
440    
441                    // The first time we receive we get the port number and
442                    // answering host address (for hosts with multiple IPs)
443                    if (justStarted)
444                    {
445                        justStarted = false;
446                        hostPort = received.getPort();
447                        data.setPort(hostPort);
448                        if(!host.equals(received.getAddress()))
449                        {
450                            host = received.getAddress();
451                            data.setAddress(host);
452                            sent.setAddress(host);
453                        }
454                    }
455    
456                    // Comply with RFC 783 indication that an error acknowledgement
457                    // should be sent to originator if unexpected TID or host.
458                    if (host.equals(received.getAddress()) &&
459                            received.getPort() == hostPort)
460                    {
461    
462                        switch (received.getType())
463                        {
464                        case TFTPPacket.ERROR:
465                            error = (TFTPErrorPacket)received;
466                            endBufferedOps();
467                            throw new IOException("Error code " + error.getError() +
468                                                  " received: " + error.getMessage());
469                        case TFTPPacket.ACKNOWLEDGEMENT:
470                            ack = (TFTPAckPacket)received;
471    
472                            lastBlock = ack.getBlockNumber();
473    
474                            if (lastBlock == block)
475                            {
476                                ++block;
477                                if (block > 65535)
478                                {
479                                    // wrap the block number
480                                    block = 0;
481                                }
482                                if (lastAckWait) {
483    
484                                  break _sendPacket;
485                                }
486                                else {
487                                  break _receivePacket;
488                                }
489                            }
490                            else
491                            {
492                                discardPackets();
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    }