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