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            StringBuffer 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 StringBuffer(128);
195    
196            for (i = 0; i < digest.length; i++)
197                digestBuffer.append(Integer.toHexString(digest[i] & 0xff));
198    
199            buffer = new StringBuffer(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