    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.pop3;
    019
    020    import java.io.IOException;
    021    import java.io.Reader;
    022    import java.security.MessageDigest;
    023    import java.security.NoSuchAlgorithmException;
    024    import java.util.Enumeration;
    025    import java.util.StringTokenizer;
    026
    027    import org.apache.commons.net.io.DotTerminatedMessageReader;
    028
    029    /***
    030     * The POP3Client class implements the client side of the Internet POP3
    031     * Protocol defined in RFC 1939.  All commands are supported, including
    032     * the APOP command which requires MD5 encryption.  See RFC 1939 for
    033     * more details on the POP3 protocol.
    034     * <p>
    035     * Rather than list it separately for each method, we mention here that
    036     * every method communicating with the server and throwing an IOException
    037     * can also throw a
    038     * {@link org.apache.commons.net.MalformedServerReplyException}
    039     * , which is a subclass
    040     * of IOException.  A MalformedServerReplyException will be thrown when
    041     * the reply received from the server deviates enough from the protocol
    042     * specification that it cannot be interpreted in a useful manner despite
    043     * attempts to be as lenient as possible.
    044     * <p>
    045     * <p>
    046     * @author Daniel F. Savarese
    047     * @see POP3MessageInfo
    048     * @see org.apache.commons.net.io.DotTerminatedMessageReader
    049     * @see org.apache.commons.net.MalformedServerReplyException
    050     ***/
    051
    052    public class POP3Client extends POP3
    053    {
    054
    055        private static POP3MessageInfo __parseStatus(String line)
    056        {
    057            int num, size;
    058            StringTokenizer tokenizer;
    059
    060            tokenizer = new StringTokenizer(line);
    061
    062            if (!tokenizer.hasMoreElements())
    063                return null;
    064
    065            num = size = 0;
    066
    067            try
    068            {
    069                num = Integer.parseInt(tokenizer.nextToken());
    070
    071                if (!tokenizer.hasMoreElements())
    072                    return null;
    073
    074                size = Integer.parseInt(tokenizer.nextToken());
    075            }
    076            catch (NumberFormatException e)
    077            {
    078                return null;
    079            }
    080
    081            return new POP3MessageInfo(num, size);
    082        }
    083
    084        private static POP3MessageInfo __parseUID(String line)
    085        {
    086            int num;
    087            StringTokenizer tokenizer;
    088
    089            tokenizer = new StringTokenizer(line);
    090
    091            if (!tokenizer.hasMoreElements())
    092                return null;
    093
    094            num = 0;
    095
    096            try
    097            {
    098                num = Integer.parseInt(tokenizer.nextToken());
    099
    100                if (!tokenizer.hasMoreElements())
    101                    return null;
    102
    103                line = tokenizer.nextToken();
    104            }
    105            catch (NumberFormatException e)
    106            {
    107                return null;
    108            }
    109
    110            return new POP3MessageInfo(num, line);
    111        }
    112
    113        /***
    114         * Login to the POP3 server with the given username and password.  You
    115         * must first connect to the server with
    116         * {@link org.apache.commons.net.SocketClient#connect  connect }
    117         * before attempting to login.  A login attempt is only valid if
    118         * the client is in the
    119         * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
    120         * .  After logging in, the client enters the
    121         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    122         * .
    123         * <p>
    124         * @param username  The account name being logged in to.
    125         * @param password  The plain text password of the account.
    126         * @return True if the login attempt was successful, false if not.
    127         * @exception IOException If a network I/O error occurs in the process of
    128         *            logging in.
    129         ***/
    130        public boolean login(String username, String password) throws IOException
    131        {
    132            if (getState() != AUTHORIZATION_STATE)
    133                return false;
    134
    135            if (sendCommand(POP3Command.USER, username) != POP3Reply.OK)
    136                return false;
    137
    138            if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK)
    139                return false;
    140
    141            setState(TRANSACTION_STATE);
    142
    143            return true;
    144        }
    145
    146
    147        /***
    148         * Login to the POP3 server with the given username and authentication
    149         * information.  Use this method when connecting to a server requiring
    150         * authentication using the APOP command.  Because the timestamp
    151         * produced in the greeting banner varies from server to server, it is
    152         * not possible to consistently extract the information.  Therefore,
    153         * after connecting to the server, you must call
    154         * {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString }
    155         *  and parse out the timestamp information yourself.
    156         * <p>
    157         * You must first connect to the server with
    158         * {@link org.apache.commons.net.SocketClient#connect  connect }
    159         * before attempting to login.  A login attempt is only valid if
    160         * the client is in the
    161         * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
    162         * .  After logging in, the client enters the
    163         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    164         * .  After connecting, you must parse out the
    165         * server specific information to use as a timestamp, and pass that
    166         * information to this method.  The secret is a shared secret known
    167         * to you and the server.  See RFC 1939 for more details regarding
    168         * the APOP command.
    169         * <p>
    170         * @param username  The account name being logged in to.
    171         * @param timestamp  The timestamp string to combine with the secret.
    172         * @param secret  The shared secret which produces the MD5 digest when
    173         *        combined with the timestamp.
    174         * @return True if the login attempt was successful, false if not.
    175         * @exception IOException If a network I/O error occurs in the process of
    176         *            logging in.
    177         * @exception NoSuchAlgorithmException If the MD5 encryption algorithm
    178         *      cannot be instantiated by the Java runtime system.
    179         ***/
    180        public boolean login(String username, String timestamp, String secret)
    181        throws IOException, NoSuchAlgorithmException
    182        {
    183            int i;
    184            byte[] digest;
    185            StringBuilder buffer, digestBuffer;
    186            MessageDigest md5;
    187
    188            if (getState() != AUTHORIZATION_STATE)
    189                return false;
    190
    191            md5 = MessageDigest.getInstance("MD5");
    192            timestamp += secret;
    193            digest = md5.digest(timestamp.getBytes());
    194            digestBuffer = new StringBuilder(128);
    195
    196            for (i = 0; i < digest.length; i++)
    197                digestBuffer.append(Integer.toHexString(digest[i] & 0xff));
    198
    199            buffer = new StringBuilder(256);
    200            buffer.append(username);
    201            buffer.append(' ');
    202            buffer.append(digestBuffer.toString());
    203
    204            if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK)
    205                return false;
    206
    207            setState(TRANSACTION_STATE);
    208
    209            return true;
    210        }
    211
    212
    213        /***
    214         * Logout of the POP3 server.  To fully disconnect from the server
    215         * you must call
    216         * {@link org.apache.commons.net.pop3.POP3#disconnect  disconnect }.
    217         * A logout attempt is valid in any state.  If
    218         * the client is in the
    219         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    220         * , it enters the
    221         * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE }
    222         *  on a successful logout.
    223         * <p>
    224         * @return True if the logout attempt was successful, false if not.
    225         * @exception IOException If a network I/O error occurs in the process
    226         *           of logging out.
    227         ***/
    228        public boolean logout() throws IOException
    229        {
    230            if (getState() == TRANSACTION_STATE)
    231                setState(UPDATE_STATE);
    232            sendCommand(POP3Command.QUIT);
    233            return (_replyCode == POP3Reply.OK);
    234        }
    235
    236
    237        /***
    238         * Send a NOOP command to the POP3 server.  This is useful for keeping
    239         * a connection alive since most POP3 servers will timeout after 10
    240         * minutes of inactivity.  A noop attempt will only succeed if
    241         * the client is in the
    242         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    243         * .
    244         * <p>
    245         * @return True if the noop attempt was successful, false if not.
    246         * @exception IOException If a network I/O error occurs in the process of
    247         *        sending the NOOP command.
    248         ***/
    249        public boolean noop() throws IOException
    250        {
    251            if (getState() == TRANSACTION_STATE)
    252                return (sendCommand(POP3Command.NOOP) == POP3Reply.OK);
    253            return false;
    254        }
    255
    256
    257        /***
    258         * Delete a message from the POP3 server.  The message is only marked
    259         * for deletion by the server.  If you decide to unmark the message, you
    260         * must issuse a {@link #reset  reset } command.  Messages marked
    261         * for deletion are only deleted by the server on
    262         * {@link #logout  logout }.
    263         * A delete attempt can only succeed if the client is in the
    264         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    265         * .
    266         * <p>
    267         * @param messageId  The message number to delete.
    268         * @return True if the deletion attempt was successful, false if not.
    269         * @exception IOException If a network I/O error occurs in the process of
    270         *           sending the delete command.
    271         ***/
    272        public boolean deleteMessage(int messageId) throws IOException
    273        {
    274            if (getState() == TRANSACTION_STATE)
    275                return (sendCommand(POP3Command.DELE, Integer.toString(messageId))
    276                        == POP3Reply.OK);
    277            return false;
    278        }
    279
    280
    281        /***
    282         * Reset the POP3 session.  This is useful for undoing any message
    283         * deletions that may have been performed.  A reset attempt can only
    284         * succeed if the client is in the
    285         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    286         * .
    287         * <p>
    288         * @return True if the reset attempt was successful, false if not.
    289         * @exception IOException If a network I/O error occurs in the process of
    290         *      sending the reset command.
    291         ***/
    292        public boolean reset() throws IOException
    293        {
    294            if (getState() == TRANSACTION_STATE)
    295                return (sendCommand(POP3Command.RSET) == POP3Reply.OK);
    296            return false;
    297        }
    298
    299        /***
    300         * Get the mailbox status.  A status attempt can only
    301         * succeed if the client is in the
    302         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    303         * .  Returns a POP3MessageInfo instance
    304         * containing the number of messages in the mailbox and the total
    305         * size of the messages in bytes.  Returns null if the status the
    306         * attempt fails.
    307         * <p>
    308         * @return A POP3MessageInfo instance containing the number of
    309         *         messages in the mailbox and the total size of the messages
    310         *         in bytes.  Returns null if the status the attempt fails.
    311         * @exception IOException If a network I/O error occurs in the process of
    312         *       sending the status command.
    313         ***/
    314        public POP3MessageInfo status() throws IOException
    315        {
    316            if (getState() != TRANSACTION_STATE)
    317                return null;
    318            if (sendCommand(POP3Command.STAT) != POP3Reply.OK)
    319                return null;
    320            return __parseStatus(_lastReplyLine.substring(3));
    321        }
    322
    323
    324        /***
    325         * List an individual message.  A list attempt can only
    326         * succeed if the client is in the
    327         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    328         * .  Returns a POP3MessageInfo instance
    329         * containing the number of the listed message and the
    330         * size of the message in bytes.  Returns null if the list
    331         * attempt fails (e.g., if the specified message number does
    332         * not exist).
    333         * <p>
    334         * @param messageId  The number of the message list.
    335         * @return A POP3MessageInfo instance containing the number of the
    336         *         listed message and the size of the message in bytes.  Returns
    337         *         null if the list attempt fails.
    338         * @exception IOException If a network I/O error occurs in the process of
    339         *         sending the list command.
    340         ***/
    341        public POP3MessageInfo listMessage(int messageId) throws IOException
    342        {
    343            if (getState() != TRANSACTION_STATE)
    344                return null;
    345            if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
    346                    != POP3Reply.OK)
    347                return null;
    348            return __parseStatus(_lastReplyLine.substring(3));
    349        }
    350
    351
    352        /***
    353         * List all messages.  A list attempt can only
    354         * succeed if the client is in the
    355         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    356         * .  Returns an array of POP3MessageInfo instances,
    357         * each containing the number of a message and its size in bytes.
    358         * If there are no messages, this method returns a zero length array.
    359         * If the list attempt fails, it returns null.
    360         * <p>
    361         * @return An array of POP3MessageInfo instances representing all messages
    362         * in the order they appear in the mailbox,
    363         * each containing the number of a message and its size in bytes.
    364         * If there are no messages, this method returns a zero length array.
    365         * If the list attempt fails, it returns null.
    366         * @exception IOException If a network I/O error occurs in the process of
    367         *     sending the list command.
    368         ***/
    369        public POP3MessageInfo[] listMessages() throws IOException
    370        {
    371            POP3MessageInfo[] messages;
    372            Enumeration<String> en;
    373            int line;
    374
    375            if (getState() != TRANSACTION_STATE)
    376                return null;
    377            if (sendCommand(POP3Command.LIST) != POP3Reply.OK)
    378                return null;
    379            getAdditionalReply();
    380
    381            // This could be a zero length array if no messages present
    382            messages = new POP3MessageInfo[_replyLines.size() - 2];
    383            en = _replyLines.elements();
    384
    385            // Skip first line
    386            en.nextElement();
    387
    388            // Fetch lines.
    389            for (line = 0; line < messages.length; line++)
    390                messages[line] = __parseStatus(en.nextElement());
    391
    392            return messages;
    393        }
    394
    395        /***
    396         * List the unique identifier for a message.  A list attempt can only
    397         * succeed if the client is in the
    398         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    399         * .  Returns a POP3MessageInfo instance
    400         * containing the number of the listed message and the
    401         * unique identifier for that message.  Returns null if the list
    402         * attempt fails  (e.g., if the specified message number does
    403         * not exist).
    404         * <p>
    405         * @param messageId  The number of the message list.
    406         * @return A POP3MessageInfo instance containing the number of the
    407         *         listed message and the unique identifier for that message.
    408         *         Returns null if the list attempt fails.
    409         * @exception IOException If a network I/O error occurs in the process of
    410         *        sending the list unique identifier command.
    411         ***/
    412        public POP3MessageInfo listUniqueIdentifier(int messageId)
    413        throws IOException
    414        {
    415            if (getState() != TRANSACTION_STATE)
    416                return null;
    417            if (sendCommand(POP3Command.UIDL, Integer.toString(messageId))
    418                    != POP3Reply.OK)
    419                return null;
    420            return __parseUID(_lastReplyLine.substring(3));
    421        }
    422
    423
    424        /***
    425         * List the unique identifiers for all messages.  A list attempt can only
    426         * succeed if the client is in the
    427         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    428         * .  Returns an array of POP3MessageInfo instances,
    429         * each containing the number of a message and its unique identifier.
    430         * If there are no messages, this method returns a zero length array.
    431         * If the list attempt fails, it returns null.
    432         * <p>
    433         * @return An array of POP3MessageInfo instances representing all messages
    434         * in the order they appear in the mailbox,
    435         * each containing the number of a message and its unique identifier
    436         * If there are no messages, this method returns a zero length array.
    437         * If the list attempt fails, it returns null.
    438         * @exception IOException If a network I/O error occurs in the process of
    439         *     sending the list unique identifier command.
    440         ***/
    441        public POP3MessageInfo[] listUniqueIdentifiers() throws IOException
    442        {
    443            POP3MessageInfo[] messages;
    444            Enumeration<String> en;
    445            int line;
    446
    447            if (getState() != TRANSACTION_STATE)
    448                return null;
    449            if (sendCommand(POP3Command.UIDL) != POP3Reply.OK)
    450                return null;
    451            getAdditionalReply();
    452
    453            // This could be a zero length array if no messages present
    454            messages = new POP3MessageInfo[_replyLines.size() - 2];
    455            en = _replyLines.elements();
    456
    457            // Skip first line
    458            en.nextElement();
    459
    460            // Fetch lines.
    461            for (line = 0; line < messages.length; line++)
    462                messages[line] = __parseUID(en.nextElement());
    463
    464            return messages;
    465        }
    466
    467
    468        /***
    469         * Retrieve a message from the POP3 server.  A retrieve message attempt
    470         * can only succeed if the client is in the
    471         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    472         * .  Returns a DotTerminatedMessageReader instance
    473         * from which the entire message can be read.
    474         * Returns null if the retrieval attempt fails  (e.g., if the specified
    475         * message number does not exist).
    476         * <p>
    477         * You must not issue any commands to the POP3 server (i.e., call any
    478         * other methods) until you finish reading the message from the
    479         * returned Reader instance.
    480         * The POP3 protocol uses the same stream for issuing commands as it does
    481         * for returning results.  Therefore the returned Reader actually reads
    482         * directly from the POP3 connection.  After the end of message has been
    483         * reached, new commands can be executed and their replies read.  If
    484         * you do not follow these requirements, your program will not work
    485         * properly.
    486         * <p>
    487         * @param messageId  The number of the message to fetch.
    488         * @return A DotTerminatedMessageReader instance
    489         * from which the entire message can be read.
    490         * Returns null if the retrieval attempt fails  (e.g., if the specified
    491         * message number does not exist).
    492         * @exception IOException If a network I/O error occurs in the process of
    493         *        sending the retrieve message command.
    494         ***/
    495        public Reader retrieveMessage(int messageId) throws IOException
    496        {
    497            if (getState() != TRANSACTION_STATE)
    498                return null;
    499            if (sendCommand(POP3Command.RETR, Integer.toString(messageId))
    500                    != POP3Reply.OK)
    501                return null;
    502
    503            return new DotTerminatedMessageReader(_reader);
    504        }
    505
    506
    507        /***
    508         * Retrieve only the specified top number of lines of a message from the
    509         * POP3 server.  A retrieve top lines attempt
    510         * can only succeed if the client is in the
    511         * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    512         * .  Returns a DotTerminatedMessageReader instance
    513         * from which the specified top number of lines of the message can be
    514         * read.
    515         * Returns null if the retrieval attempt fails  (e.g., if the specified
    516         * message number does not exist).
    517         * <p>
    518         * You must not issue any commands to the POP3 server (i.e., call any
    519         * other methods) until you finish reading the message from the returned
    520         * Reader instance.
    521         * The POP3 protocol uses the same stream for issuing commands as it does
    522         * for returning results.  Therefore the returned Reader actually reads
    523         * directly from the POP3 connection.  After the end of message has been
    524         * reached, new commands can be executed and their replies read.  If
    525         * you do not follow these requirements, your program will not work
    526         * properly.
    527         * <p>
    528         * @param messageId  The number of the message to fetch.
    529         * @param numLines  The top number of lines to fetch. This must be >= 0.
    530         * @return  A DotTerminatedMessageReader instance
    531         * from which the specified top number of lines of the message can be
    532         * read.
    533         * Returns null if the retrieval attempt fails  (e.g., if the specified
    534         * message number does not exist).
    535         * @exception IOException If a network I/O error occurs in the process of
    536         *       sending the top command.
    537         ***/
    538        public Reader retrieveMessageTop(int messageId, int numLines)
    539        throws IOException
    540        {
    541            if (numLines < 0 || getState() != TRANSACTION_STATE)
    542                return null;
    543            if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
    544                            Integer.toString(numLines)) != POP3Reply.OK)
    545                return null;
    546
    547            return new DotTerminatedMessageReader(_reader);
    548        }
    549
    550
    551    }
    552