    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    }