    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
    018package org.apache.commons.net.tftp;
    019
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.InterruptedIOException;
    023import java.io.OutputStream;
    024import java.net.InetAddress;
    025import java.net.SocketException;
    026import java.net.UnknownHostException;
    027import org.apache.commons.net.io.FromNetASCIIOutputStream;
    028import 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
    056public 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 acknowledgment
    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 acknowledgment
    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}