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.smtp; 019 020import java.io.IOException; 021import java.net.InetAddress; 022import java.security.InvalidKeyException; 023import java.security.NoSuchAlgorithmException; 024import java.security.spec.InvalidKeySpecException; 025import javax.crypto.Mac; 026import javax.crypto.spec.SecretKeySpec; 027 028import org.apache.commons.net.util.Base64; 029 030 031/** 032 * An SMTP Client class with authentication support (RFC4954). 033 * 034 * @see SMTPClient 035 * @since 3.0 036 */ 037public class AuthenticatingSMTPClient extends SMTPSClient 038{ 039 /** 040 * The default AuthenticatingSMTPClient constructor. 041 * Creates a new Authenticating SMTP Client. 042 * @throws NoSuchAlgorithmException 043 */ 044 public AuthenticatingSMTPClient() throws NoSuchAlgorithmException 045 { 046 super(); 047 } 048 049 /** 050 * Overloaded constructor that takes a protocol specification 051 * @param protocol The protocol to use 052 * @throws NoSuchAlgorithmException 053 */ 054 public AuthenticatingSMTPClient(String protocol) throws NoSuchAlgorithmException { 055 super(protocol); 056 } 057 058 /*** 059 * A convenience method to send the ESMTP EHLO command to the server, 060 * receive the reply, and return the reply code. 061 * <p> 062 * @param hostname The hostname of the sender. 063 * @return The reply code received from the server. 064 * @exception SMTPConnectionClosedException 065 * If the SMTP server prematurely closes the connection as a result 066 * of the client being idle or some other reason causing the server 067 * to send SMTP reply code 421. This exception may be caught either 068 * as an IOException or independently as itself. 069 * @exception IOException If an I/O error occurs while either sending the 070 * command or receiving the server reply. 071 ***/ 072 public int ehlo(String hostname) throws IOException 073 { 074 return sendCommand(SMTPCommand.EHLO, hostname); 075 } 076 077 /*** 078 * Login to the ESMTP server by sending the EHLO command with the 079 * given hostname as an argument. Before performing any mail commands, 080 * you must first login. 081 * <p> 082 * @param hostname The hostname with which to greet the SMTP server. 083 * @return True if successfully completed, false if not. 084 * @exception SMTPConnectionClosedException 085 * If the SMTP server prematurely closes the connection as a result 086 * of the client being idle or some other reason causing the server 087 * to send SMTP reply code 421. This exception may be caught either 088 * as an IOException or independently as itself. 089 * @exception IOException If an I/O error occurs while either sending a 090 * command to the server or receiving a reply from the server. 091 ***/ 092 public boolean elogin(String hostname) throws IOException 093 { 094 return SMTPReply.isPositiveCompletion(ehlo(hostname)); 095 } 096 097 098 /*** 099 * Login to the ESMTP server by sending the EHLO command with the 100 * client hostname as an argument. Before performing any mail commands, 101 * you must first login. 102 * <p> 103 * @return True if successfully completed, false if not. 104 * @exception SMTPConnectionClosedException 105 * If the SMTP server prematurely closes the connection as a result 106 * of the client being idle or some other reason causing the server 107 * to send SMTP reply code 421. This exception may be caught either 108 * as an IOException or independently as itself. 109 * @exception IOException If an I/O error occurs while either sending a 110 * command to the server or receiving a reply from the server. 111 ***/ 112 public boolean elogin() throws IOException 113 { 114 String name; 115 InetAddress host; 116 117 host = getLocalAddress(); 118 name = host.getHostName(); 119 120 if (name == null) { 121 return false; 122 } 123 124 return SMTPReply.isPositiveCompletion(ehlo(name)); 125 } 126 127 /*** 128 * Returns the integer values of the enhanced reply code of the last SMTP reply. 129 * @return The integer values of the enhanced reply code of the last SMTP reply. 130 * First digit is in the first array element. 131 ***/ 132 public int[] getEnhancedReplyCode() 133 { 134 String reply = getReplyString().substring(4); 135 String[] parts = reply.substring(0, reply.indexOf(' ')).split ("\\."); 136 int[] res = new int[parts.length]; 137 for (int i = 0; i < parts.length; i++) 138 { 139 res[i] = Integer.parseInt (parts[i]); 140 } 141 return res; 142 } 143 144 /*** 145 * Authenticate to the SMTP server by sending the AUTH command with the 146 * selected mechanism, using the given username and the given password. 147 * <p> 148 * @return True if successfully completed, false if not. 149 * @exception SMTPConnectionClosedException 150 * If the SMTP server prematurely closes the connection as a result 151 * of the client being idle or some other reason causing the server 152 * to send SMTP reply code 421. This exception may be caught either 153 * as an IOException or independently as itself. 154 * @exception IOException If an I/O error occurs while either sending a 155 * command to the server or receiving a reply from the server. 156 * @exception NoSuchAlgorithmException If the CRAM hash algorithm 157 * cannot be instantiated by the Java runtime system. 158 * @exception InvalidKeyException If the CRAM hash algorithm 159 * failed to use the given password. 160 * @exception InvalidKeySpecException If the CRAM hash algorithm 161 * failed to use the given password. 162 ***/ 163 public boolean auth(AuthenticatingSMTPClient.AUTH_METHOD method, 164 String username, String password) 165 throws IOException, NoSuchAlgorithmException, 166 InvalidKeyException, InvalidKeySpecException 167 { 168 if (!SMTPReply.isPositiveIntermediate(sendCommand(SMTPCommand.AUTH, 169 AUTH_METHOD.getAuthName(method)))) { 170 return false; 171 } 172 173 if (method.equals(AUTH_METHOD.PLAIN)) 174 { 175 // the server sends an empty response ("334 "), so we don't have to read it. 176 return SMTPReply.isPositiveCompletion(sendCommand( 177 new String( 178 Base64.encodeBase64(("\000" + username + "\000" + password).getBytes()) 179 ) 180 )); 181 } 182 else if (method.equals(AUTH_METHOD.CRAM_MD5)) 183 { 184 // get the CRAM challenge 185 byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(4).trim()); 186 // get the Mac instance 187 Mac hmac_md5 = Mac.getInstance("HmacMD5"); 188 hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5")); 189 // compute the result: 190 byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(); 191 // join the byte arrays to form the reply 192 byte[] usernameBytes = username.getBytes(); 193 byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; 194 System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); 195 toEncode[usernameBytes.length] = ' '; 196 System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); 197 // send the reply and read the server code: 198 return SMTPReply.isPositiveCompletion(sendCommand( 199 new String(Base64.encodeBase64(toEncode)))); 200 } 201 else if (method.equals(AUTH_METHOD.LOGIN)) 202 { 203 // the server sends fixed responses (base64("Username") and 204 // base64("Password")), so we don't have to read them. 205 if (!SMTPReply.isPositiveIntermediate(sendCommand( 206 new String(Base64.encodeBase64(username.getBytes()))))) { 207 return false; 208 } 209 return SMTPReply.isPositiveCompletion(sendCommand( 210 new String(Base64.encodeBase64(password.getBytes())))); 211 } else { 212 return false; // safety check 213 } 214 } 215 216 /** 217 * Converts the given byte array to a String containing the hex values of the bytes. 218 * For example, the byte 'A' will be converted to '41', because this is the ASCII code 219 * (and the byte value) of the capital letter 'A'. 220 * @param a The byte array to convert. 221 * @return The resulting String of hex codes. 222 */ 223 private String _convertToHexString(byte[] a) 224 { 225 StringBuilder result = new StringBuilder(a.length*2); 226 for (int i = 0; i < a.length; i++) 227 { 228 if ( (a[i] & 0x0FF) <= 15 ) { 229 result.append("0"); 230 } 231 result.append(Integer.toHexString(a[i] & 0x0FF)); 232 } 233 return result.toString(); 234 } 235 236 /** 237 * The enumeration of currently-supported authentication methods. 238 */ 239 public static enum AUTH_METHOD 240 { 241 /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ 242 PLAIN, 243 /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ 244 CRAM_MD5, 245 /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */ 246 LOGIN; 247 248 /** 249 * Gets the name of the given authentication method suitable for the server. 250 * @param method The authentication method to get the name for. 251 * @return The name of the given authentication method suitable for the server. 252 */ 253 public static final String getAuthName(AUTH_METHOD method) 254 { 255 if (method.equals(AUTH_METHOD.PLAIN)) { 256 return "PLAIN"; 257 } else if (method.equals(AUTH_METHOD.CRAM_MD5)) { 258 return "CRAM-MD5"; 259 } else if (method.equals(AUTH_METHOD.LOGIN)) { 260 return "LOGIN"; 261 } else { 262 return null; 263 } 264 } 265 } 266} 267 268/* kate: indent-width 4; replace-tabs on; */