001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package javax.mail; 021 022 import java.net.InetAddress; 023 import java.net.UnknownHostException; 024 import java.util.Vector; 025 026 import javax.mail.event.ConnectionEvent; 027 import javax.mail.event.ConnectionListener; 028 import javax.mail.event.MailEvent; 029 030 /** 031 * @version $Rev: 510527 $ $Date: 2007-02-22 15:17:48 +0100 (Do, 22. Feb 2007) $ 032 */ 033 public abstract class Service { 034 /** 035 * The session from which this service was created. 036 */ 037 protected Session session; 038 /** 039 * The URLName of this service 040 */ 041 protected URLName url; 042 /** 043 * Debug flag for this service, set from the Session's debug flag. 044 */ 045 protected boolean debug; 046 047 private boolean connected; 048 private final Vector connectionListeners = new Vector(2); 049 private final EventQueue queue = new EventQueue(); 050 051 /** 052 * Construct a new Service. 053 * @param session the session from which this service was created 054 * @param url the URLName of this service 055 */ 056 protected Service(Session session, URLName url) { 057 this.session = session; 058 this.url = url; 059 this.debug = session.getDebug(); 060 } 061 062 /** 063 * A generic connect method that takes no parameters allowing subclasses 064 * to implement an appropriate authentication scheme. 065 * The default implementation calls <code>connect(null, null, null)</code> 066 * @throws AuthenticationFailedException if authentication fails 067 * @throws MessagingException for other failures 068 */ 069 public void connect() throws MessagingException { 070 connect(null, null, null); 071 } 072 073 /** 074 * Connect to the specified host using a simple username/password authenticaion scheme 075 * and the default port. 076 * The default implementation calls <code>connect(host, -1, user, password)</code> 077 * 078 * @param host the host to connect to 079 * @param user the user name 080 * @param password the user's password 081 * @throws AuthenticationFailedException if authentication fails 082 * @throws MessagingException for other failures 083 */ 084 public void connect(String host, String user, String password) throws MessagingException { 085 connect(host, -1, user, password); 086 } 087 088 /** 089 * Connect to the specified host at the specified port using a simple username/password authenticaion scheme. 090 * 091 * If this Service is already connected, an IllegalStateException is thrown. 092 * 093 * @param host the host to connect to 094 * @param port the port to connect to; pass -1 to use the default for the protocol 095 * @param user the user name 096 * @param password the user's password 097 * @throws AuthenticationFailedException if authentication fails 098 * @throws MessagingException for other failures 099 * @throws IllegalStateException if this service is already connected 100 */ 101 public void connect(String host, int port, String user, String password) throws MessagingException { 102 103 if (isConnected()) { 104 throw new IllegalStateException("Already connected"); 105 } 106 107 // before we try to connect, we need to derive values for some parameters that may not have 108 // been explicitly specified. For example, the normal connect() method leaves us to derive all 109 // of these from other sources. Some of the values are derived from our URLName value, others 110 // from session parameters. We need to go through all of these to develop a set of values we 111 // can connect with. 112 113 // this is the protocol we're connecting with. We use this largely to derive configured values from 114 // session properties. 115 String protocol = null; 116 117 // if we're working with the URL form, then we can retrieve the protocol from the URL. 118 if (url != null) { 119 protocol = url.getProtocol(); 120 } 121 122 // if the port is -1, see if we have an override from url. 123 if (port == -1) { 124 if (protocol != null) { 125 port = url.getPort(); 126 } 127 } 128 129 // now try to derive values for any of the arguments we've been given as defaults 130 if (host == null) { 131 // first choice is from the url, if we have 132 if (url != null) { 133 host = url.getHost(); 134 // it is possible that this could return null (rare). If it does, try to get a 135 // value from a protocol specific session variable. 136 if (host == null) { 137 if (protocol != null) { 138 host = session.getProperty("mail." + protocol + ".host"); 139 } 140 } 141 } 142 // this may still be null...get the global mail property 143 if (host == null) { 144 host = session.getProperty("mail.host"); 145 } 146 } 147 148 // ok, go after userid information next. 149 if (user == null) { 150 // first choice is from the url, if we have 151 if (url != null) { 152 user = url.getUsername(); 153 // make sure we get the password from the url, if we can. 154 if (password == null) { 155 password = url.getPassword(); 156 } 157 // user still null? We have several levels of properties to try yet 158 if (user == null) { 159 if (protocol != null) { 160 user = session.getProperty("mail." + protocol + ".user"); 161 } 162 } 163 } 164 165 // this may still be null...get the global mail property 166 if (user == null) { 167 user = session.getProperty("mail.user"); 168 } 169 170 // finally, we try getting the system defined user name 171 try { 172 user = System.getProperty("user.name"); 173 } catch (SecurityException e) { 174 // we ignore this, and just us a null username. 175 } 176 } 177 // if we have an explicitly given user name, we need to see if this matches the url one and 178 // grab the password from there. 179 else { 180 if (url != null && user.equals(url.getUsername())) { 181 password = url.getPassword(); 182 } 183 } 184 185 // we need to update the URLName associated with this connection once we have all of the information, 186 // which means we also need to propogate the file portion of the URLName if we have this form when 187 // we start. 188 String file = null; 189 if (url != null) { 190 file = url.getFile(); 191 } 192 193 // see if we have cached security information to use. If this is not cached, we'll save it 194 // after we successfully connect. 195 boolean cachePassword = false; 196 197 198 // still have a null password to this point, and using a url form? 199 if (password == null && url != null) { 200 // construct a new URL, filling in any pieces that may have been explicitly specified. 201 setURLName(new URLName(protocol, host, port, file, user, password)); 202 // now see if we have a saved password from a previous request. 203 PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName()); 204 205 // if we found a saved one, see if we need to get any the pieces from here. 206 if (cachedPassword != null) { 207 // not even a resolved userid? Then use both bits. 208 if (user == null) { 209 user = cachedPassword.getUserName(); 210 password = cachedPassword.getPassword(); 211 } 212 // our user name must match the cached name to be valid. 213 else if (user.equals(cachedPassword.getUserName())) { 214 password = cachedPassword.getPassword(); 215 } 216 } 217 else 218 { 219 // nothing found in the cache, so we need to save this if we can connect successfully. 220 cachePassword = true; 221 } 222 } 223 224 // we've done our best up to this point to obtain all of the information needed to make the 225 // connection. Now we pass this off to the protocol handler to see if it works. If we get a 226 // connection failure, we may need to prompt for a password before continuing. 227 try { 228 connected = protocolConnect(host, port, user, password); 229 } 230 catch (AuthenticationFailedException e) { 231 e.printStackTrace(); 232 } 233 234 if (!connected) { 235 InetAddress ipAddress = null; 236 237 try { 238 ipAddress = InetAddress.getByName(host); 239 } catch (UnknownHostException e) { 240 } 241 242 // now ask the session to try prompting for a password. 243 PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user); 244 245 // if we were able to obtain new information from the session, then try again using the 246 // provided information . 247 if (promptPassword != null) { 248 user = promptPassword.getUserName(); 249 password = promptPassword.getPassword(); 250 } 251 connected = protocolConnect(host, port, user, password); 252 } 253 254 255 // if we're still not connected, then this is an exception. 256 if (!connected) { 257 throw new AuthenticationFailedException(); 258 } 259 260 // the URL name needs to reflect the most recent information. 261 setURLName(new URLName(protocol, host, port, file, user, password)); 262 263 // we need to update the global password cache with this information. 264 if (cachePassword) { 265 session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password)); 266 } 267 268 // we're now connected....broadcast this to any interested parties. 269 setConnected(connected); 270 notifyConnectionListeners(ConnectionEvent.OPENED); 271 } 272 273 /** 274 * Attempt the protocol-specific connection; subclasses should override this to establish 275 * a connection in the appropriate manner. 276 * 277 * This method should return true if the connection was established. 278 * It may return false to cause the {@link #connect(String, int, String, String)} method to 279 * reattempt the connection after trying to obtain user and password information from the user. 280 * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt. 281 * 282 * @param host 283 * @param port 284 * @param user 285 * @param password 286 * @return 287 * @throws AuthenticationFailedException if authentication fails 288 * @throws MessagingException for other failures 289 */ 290 protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException { 291 return false; 292 } 293 294 /** 295 * Check if this service is currently connected. 296 * The default implementation simply returns the value of a private boolean field; 297 * subclasses may wish to override this method to verify the physical connection. 298 * 299 * @return true if this service is connected 300 */ 301 public boolean isConnected() { 302 return connected; 303 } 304 305 /** 306 * Notification to subclasses that the connection state has changed. 307 * This method is called by the connect() and close() methods to indicate state change; 308 * subclasses should also call this method if the connection is automatically closed 309 * for some reason. 310 * 311 * @param connected the connection state 312 */ 313 protected void setConnected(boolean connected) { 314 this.connected = connected; 315 } 316 317 /** 318 * Close this service and terminate its physical connection. 319 * The default implementation simply calls setConnected(false) and then 320 * sends a CLOSED event to all registered ConnectionListeners. 321 * Subclasses overriding this method should still ensure it is closed; they should 322 * also ensure that it is called if the connection is closed automatically, for 323 * for example in a finalizer. 324 * 325 *@throws MessagingException if there were errors closing; the connection is still closed 326 */ 327 public void close() throws MessagingException { 328 setConnected(false); 329 notifyConnectionListeners(ConnectionEvent.CLOSED); 330 } 331 332 /** 333 * Return a copy of the URLName representing this service with the password and file information removed. 334 * 335 * @return the URLName for this service 336 */ 337 public URLName getURLName() { 338 339 return url == null ? null : new URLName(url.getProtocol(), url.getHost(), url.getPort(), null, url.getUsername(), null); 340 } 341 342 /** 343 * Set the url field. 344 * @param url the new value 345 */ 346 protected void setURLName(URLName url) { 347 this.url = url; 348 } 349 350 public void addConnectionListener(ConnectionListener listener) { 351 connectionListeners.add(listener); 352 } 353 354 public void removeConnectionListener(ConnectionListener listener) { 355 connectionListeners.remove(listener); 356 } 357 358 protected void notifyConnectionListeners(int type) { 359 queue.queueEvent(new ConnectionEvent(this, type), connectionListeners); 360 } 361 362 public String toString() { 363 return url == null ? super.toString() : url.toString(); 364 } 365 366 protected void queueEvent(MailEvent event, Vector listeners) { 367 queue.queueEvent(event, listeners); 368 } 369 370 protected void finalize() throws Throwable { 371 queue.stop(); 372 connectionListeners.clear(); 373 super.finalize(); 374 } 375 376 377 /** 378 * Package scope utility method to allow Message instances 379 * access to the Service's session. 380 * 381 * @return The Session the service is associated with. 382 */ 383 Session getSession() { 384 return session; 385 } 386 }