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.security.InvalidKeyException; 022 import java.security.NoSuchAlgorithmException; 023 import java.security.spec.InvalidKeySpecException; 024 import javax.crypto.Mac; 025 import javax.crypto.spec.SecretKeySpec; 026 027 import org.apache.commons.net.pop3.POP3Client; 028 import org.apache.commons.net.pop3.POP3Reply; 029 import org.apache.commons.net.util.Base64; 030 031 032 /** 033 * A POP3 Cilent class with protocol and authentication extensions support 034 * (RFC2449 and RFC2195). 035 * @see POP3Client 036 * @since 3.0 037 */ 038 public class ExtendedPOP3Client extends POP3SClient 039 { 040 /** 041 * The default ExtendedPOP3Client constructor. 042 * Creates a new Extended POP3 Client. 043 * @throws NoSuchAlgorithmException 044 */ 045 public ExtendedPOP3Client() throws NoSuchAlgorithmException 046 { 047 super(); 048 } 049 050 /*** 051 * Send a CAPA command to the POP3 server. 052 * @return True if the command was successful, false if not. 053 * @exception IOException If a network I/O error occurs in the process of 054 * sending the NOOP command. 055 ***/ 056 public boolean capa() throws IOException 057 { 058 return (sendCommand(POP3Command.CAPA) == POP3Reply.OK); 059 } 060 061 /*** 062 * Authenticate to the POP3 server by sending the AUTH command with the 063 * selected mechanism, using the given username and the given password. 064 * <p> 065 * @param method the {@link AUTH_METHOD} to use 066 * @param username the user name 067 * @param password the password 068 * @return True if successfully completed, false if not. 069 * @exception IOException If an I/O error occurs while either sending a 070 * command to the server or receiving a reply from the server. 071 * @exception NoSuchAlgorithmException If the CRAM hash algorithm 072 * cannot be instantiated by the Java runtime system. 073 * @exception InvalidKeyException If the CRAM hash algorithm 074 * failed to use the given password. 075 * @exception InvalidKeySpecException If the CRAM hash algorithm 076 * failed to use the given password. 077 ***/ 078 public boolean auth(AUTH_METHOD method, 079 String username, String password) 080 throws IOException, NoSuchAlgorithmException, 081 InvalidKeyException, InvalidKeySpecException 082 { 083 if (sendCommand(POP3Command.AUTH, method.getAuthName()) 084 != POP3Reply.OK_INT) return false; 085 086 switch(method) { 087 case PLAIN: 088 // the server sends an empty response ("+ "), so we don't have to read it. 089 return sendCommand( 090 new String( 091 Base64.encodeBase64(("\000" + username + "\000" + password).getBytes()) 092 ) 093 ) == POP3Reply.OK; 094 case CRAM_MD5: 095 // get the CRAM challenge 096 byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim()); 097 // get the Mac instance 098 Mac hmac_md5 = Mac.getInstance("HmacMD5"); 099 hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5")); 100 // compute the result: 101 byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(); 102 // join the byte arrays to form the reply 103 byte[] usernameBytes = username.getBytes(); 104 byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; 105 System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); 106 toEncode[usernameBytes.length] = ' '; 107 System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); 108 // send the reply and read the server code: 109 return sendCommand(new String(Base64.encodeBase64(toEncode))) == POP3Reply.OK; 110 default: 111 return false; 112 } 113 } 114 115 /** 116 * Converts the given byte array to a String containing the hex values of the bytes. 117 * For example, the byte 'A' will be converted to '41', because this is the ASCII code 118 * (and the byte value) of the capital letter 'A'. 119 * @param a The byte array to convert. 120 * @return The resulting String of hex codes. 121 */ 122 private String _convertToHexString(byte[] a) 123 { 124 StringBuilder result = new StringBuilder(a.length*2); 125 for (int i = 0; i < a.length; i++) 126 { 127 if ( (a[i] & 0x0FF) <= 15 ) result.append("0"); 128 result.append(Integer.toHexString(a[i] & 0x0FF)); 129 } 130 return result.toString(); 131 } 132 133 /** 134 * The enumeration of currently-supported authentication methods. 135 */ 136 public static enum AUTH_METHOD 137 { 138 /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ 139 PLAIN("PLAIN"), 140 141 /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ 142 CRAM_MD5("CRAM-MD5"); 143 144 private final String methodName; 145 146 AUTH_METHOD(String methodName){ 147 this.methodName = methodName; 148 } 149 /** 150 * Gets the name of the given authentication method suitable for the server. 151 * @return The name of the given authentication method suitable for the server. 152 */ 153 public final String getAuthName() 154 { 155 return this.methodName; 156 } 157 } 158 }