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