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.pop3;
019
020import java.io.IOException;
021import java.io.Reader;
022import java.security.MessageDigest;
023import java.security.NoSuchAlgorithmException;
024import java.util.Enumeration;
025import java.util.StringTokenizer;
026
027import 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
052public 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