    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    }