001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.tools; 028 import org.opends.messages.Message; 029 030 import java.io.PrintStream; 031 import java.io.IOException; 032 import java.net.ConnectException; 033 import java.net.Socket; 034 import java.net.UnknownHostException; 035 import java.util.ArrayList; 036 import java.util.concurrent.atomic.AtomicInteger; 037 038 import org.opends.server.controls.PasswordExpiringControl; 039 import org.opends.server.controls.PasswordPolicyErrorType; 040 import org.opends.server.controls.PasswordPolicyResponseControl; 041 import org.opends.server.controls.PasswordPolicyWarningType; 042 import org.opends.server.protocols.asn1.ASN1OctetString; 043 import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp; 044 import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp; 045 import org.opends.server.protocols.ldap.LDAPControl; 046 import org.opends.server.protocols.ldap.LDAPMessage; 047 import org.opends.server.protocols.ldap.UnbindRequestProtocolOp; 048 import org.opends.server.types.Control; 049 import org.opends.server.types.DebugLogLevel; 050 import org.opends.server.types.LDAPException; 051 052 import static org.opends.server.loggers.debug.DebugLogger.*; 053 import org.opends.server.loggers.debug.DebugTracer; 054 import static org.opends.messages.CoreMessages. 055 INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR; 056 import static org.opends.messages.ToolMessages.*; 057 import static org.opends.server.util.ServerConstants.*; 058 import static org.opends.server.util.StaticUtils.*; 059 import static org.opends.server.protocols.ldap.LDAPResultCode.*; 060 061 062 063 /** 064 * This class provides a tool that can be used to issue search requests to the 065 * Directory Server. 066 */ 067 public class LDAPConnection 068 { 069 /** 070 * The tracer object for the debug logger. 071 */ 072 private static final DebugTracer TRACER = getTracer(); 073 074 // The hostname to connect to. 075 private String hostName = null; 076 077 // The port number on which the directory server is accepting requests. 078 private int portNumber = 389; 079 080 private LDAPConnectionOptions connectionOptions = null; 081 private LDAPWriter ldapWriter; 082 private LDAPReader ldapReader; 083 private int versionNumber = 3; 084 085 private PrintStream out; 086 private PrintStream err; 087 088 /** 089 * Constructor for the LDAPConnection object. 090 * 091 * @param host The hostname to send the request to. 092 * @param port The port number on which the directory server is accepting 093 * requests. 094 * @param options The set of options for this connection. 095 */ 096 public LDAPConnection(String host, int port, LDAPConnectionOptions options) 097 { 098 this(host, port, options, System.out, System.err); 099 } 100 101 /** 102 * Constructor for the LDAPConnection object. 103 * 104 * @param host The hostname to send the request to. 105 * @param port The port number on which the directory server is accepting 106 * requests. 107 * @param options The set of options for this connection. 108 * @param out The print stream to use for standard output. 109 * @param err The print stream to use for standard error. 110 */ 111 public LDAPConnection(String host, int port, LDAPConnectionOptions options, 112 PrintStream out, PrintStream err) 113 { 114 this.hostName = host; 115 this.portNumber = port; 116 this.connectionOptions = options; 117 this.versionNumber = options.getVersionNumber(); 118 this.out = out; 119 this.err = err; 120 } 121 122 /** 123 * Connects to the directory server instance running on specified hostname 124 * and port number. 125 * 126 * @param bindDN The DN to bind with. 127 * @param bindPassword The password to bind with. 128 * 129 * @throws LDAPConnectionException If a problem occurs while attempting to 130 * establish the connection to the server. 131 */ 132 public void connectToHost(String bindDN, String bindPassword) 133 throws LDAPConnectionException 134 { 135 connectToHost(bindDN, bindPassword, new AtomicInteger(1)); 136 } 137 138 /** 139 * Connects to the directory server instance running on specified hostname 140 * and port number. 141 * 142 * @param bindDN The DN to bind with. 143 * @param bindPassword The password to bind with. 144 * @param nextMessageID The message ID counter that should be used for 145 * operations performed while establishing the 146 * connection. 147 * 148 * @throws LDAPConnectionException If a problem occurs while attempting to 149 * establish the connection to the server. 150 */ 151 public void connectToHost(String bindDN, String bindPassword, 152 AtomicInteger nextMessageID) 153 throws LDAPConnectionException 154 { 155 Socket socket; 156 Socket startTLSSocket = null; 157 int resultCode; 158 ArrayList<LDAPControl> requestControls = new ArrayList<LDAPControl> (); 159 ArrayList<LDAPControl> responseControls = new ArrayList<LDAPControl> (); 160 161 VerboseTracer tracer = 162 new VerboseTracer(connectionOptions.isVerbose(), err); 163 if(connectionOptions.useStartTLS()) 164 { 165 try 166 { 167 startTLSSocket = new Socket(hostName, portNumber); 168 ldapWriter = new LDAPWriter(startTLSSocket, tracer); 169 ldapReader = new LDAPReader(startTLSSocket, tracer); 170 } catch(UnknownHostException uhe) 171 { 172 Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 173 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 174 uhe); 175 } catch(ConnectException ce) 176 { 177 Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 178 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 179 ce); 180 } catch(Exception ex) 181 { 182 if (debugEnabled()) 183 { 184 TRACER.debugCaught(DebugLogLevel.ERROR, ex); 185 } 186 throw new LDAPConnectionException(Message.raw(ex.getMessage()), ex); 187 } 188 189 // Send the StartTLS extended request. 190 ExtendedRequestProtocolOp extendedRequest = 191 new ExtendedRequestProtocolOp(OID_START_TLS_REQUEST); 192 193 LDAPMessage msg = new LDAPMessage(nextMessageID.getAndIncrement(), 194 extendedRequest); 195 try 196 { 197 ldapWriter.writeMessage(msg); 198 199 // Read the response from the server. 200 msg = ldapReader.readMessage(); 201 } catch (Exception ex1) 202 { 203 if (debugEnabled()) 204 { 205 TRACER.debugCaught(DebugLogLevel.ERROR, ex1); 206 } 207 throw new LDAPConnectionException(Message.raw(ex1.getMessage()), ex1); 208 } 209 ExtendedResponseProtocolOp res = msg.getExtendedResponseProtocolOp(); 210 resultCode = res.getResultCode(); 211 if(resultCode != SUCCESS) 212 { 213 throw new LDAPConnectionException(res.getErrorMessage(), 214 resultCode, 215 res.getErrorMessage(), 216 res.getMatchedDN(), null); 217 } 218 } 219 SSLConnectionFactory sslConnectionFactory = 220 connectionOptions.getSSLConnectionFactory(); 221 try 222 { 223 if(sslConnectionFactory != null) 224 { 225 if(connectionOptions.useStartTLS()) 226 { 227 // Use existing socket. 228 socket = sslConnectionFactory.createSocket(startTLSSocket, hostName, 229 portNumber, true); 230 } else 231 { 232 socket = sslConnectionFactory.createSocket(hostName, portNumber); 233 } 234 } else 235 { 236 socket = new Socket(hostName, portNumber); 237 } 238 ldapWriter = new LDAPWriter(socket, tracer); 239 ldapReader = new LDAPReader(socket, tracer); 240 } catch(UnknownHostException uhe) 241 { 242 Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 243 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 244 uhe); 245 } catch(ConnectException ce) 246 { 247 Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 248 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 249 ce); 250 } catch(Exception ex2) 251 { 252 if (debugEnabled()) 253 { 254 TRACER.debugCaught(DebugLogLevel.ERROR, ex2); 255 } 256 throw new LDAPConnectionException(Message.raw(ex2.getMessage()), ex2); 257 } 258 259 // We need this so that we don't run out of addresses when the tool 260 // commands are called A LOT, as in the unit tests. 261 try 262 { 263 socket.setSoLinger(true, 1); 264 socket.setReuseAddress(true); 265 } catch(IOException e) 266 { 267 if (debugEnabled()) 268 { 269 TRACER.debugCaught(DebugLogLevel.ERROR, e); 270 } 271 // It doesn't matter too much if this throws, so ignore it. 272 } 273 274 if (connectionOptions.getReportAuthzID()) 275 { 276 requestControls.add(new LDAPControl(OID_AUTHZID_REQUEST)); 277 } 278 279 if (connectionOptions.usePasswordPolicyControl()) 280 { 281 requestControls.add(new LDAPControl(OID_PASSWORD_POLICY_CONTROL)); 282 } 283 284 LDAPAuthenticationHandler handler = new LDAPAuthenticationHandler( 285 ldapReader, ldapWriter, hostName, nextMessageID); 286 try 287 { 288 ASN1OctetString bindPW; 289 if (bindPassword == null) 290 { 291 bindPW = null; 292 } 293 else 294 { 295 bindPW = new ASN1OctetString(bindPassword); 296 } 297 298 String result = null; 299 if (connectionOptions.useSASLExternal()) 300 { 301 result = handler.doSASLExternal(new ASN1OctetString(bindDN), 302 connectionOptions.getSASLProperties(), 303 requestControls, responseControls); 304 } 305 else if (connectionOptions.getSASLMechanism() != null) 306 { 307 result = handler.doSASLBind(new ASN1OctetString(bindDN), bindPW, 308 connectionOptions.getSASLMechanism(), 309 connectionOptions.getSASLProperties(), 310 requestControls, responseControls); 311 } 312 else if(bindDN != null) 313 { 314 result = handler.doSimpleBind(versionNumber, 315 new ASN1OctetString(bindDN), bindPW, 316 requestControls, responseControls); 317 } 318 if(result != null) 319 { 320 out.println(result); 321 } 322 323 for (LDAPControl c : responseControls) 324 { 325 if (c.getOID().equals(OID_AUTHZID_RESPONSE)) 326 { 327 ASN1OctetString controlValue = c.getValue(); 328 if (controlValue != null) 329 { 330 331 Message message = 332 INFO_BIND_AUTHZID_RETURNED.get(controlValue.stringValue()); 333 out.println(message); 334 } 335 } 336 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED)) 337 { 338 339 Message message = INFO_BIND_PASSWORD_EXPIRED.get(); 340 out.println(message); 341 } 342 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING)) 343 { 344 PasswordExpiringControl expiringControl = 345 PasswordExpiringControl.decodeControl(new Control(c.getOID(), 346 c.isCritical(), 347 c.getValue())); 348 Message timeString = 349 secondsToTimeString(expiringControl.getSecondsUntilExpiration()); 350 351 352 Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 353 out.println(message); 354 } 355 else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL)) 356 { 357 PasswordPolicyResponseControl pwPolicyControl = 358 PasswordPolicyResponseControl.decodeControl(new Control( 359 c.getOID(), c.isCritical(), c.getValue())); 360 361 PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType(); 362 if (errorType != null) 363 { 364 switch (errorType) 365 { 366 case PASSWORD_EXPIRED: 367 368 Message message = INFO_BIND_PASSWORD_EXPIRED.get(); 369 out.println(message); 370 break; 371 case ACCOUNT_LOCKED: 372 373 message = INFO_BIND_ACCOUNT_LOCKED.get(); 374 out.println(message); 375 break; 376 case CHANGE_AFTER_RESET: 377 378 message = INFO_BIND_MUST_CHANGE_PASSWORD.get(); 379 out.println(message); 380 break; 381 } 382 } 383 384 PasswordPolicyWarningType warningType = 385 pwPolicyControl.getWarningType(); 386 if (warningType != null) 387 { 388 switch (warningType) 389 { 390 case TIME_BEFORE_EXPIRATION: 391 Message timeString = 392 secondsToTimeString(pwPolicyControl.getWarningValue()); 393 394 395 Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 396 out.println(message); 397 break; 398 case GRACE_LOGINS_REMAINING: 399 400 message = INFO_BIND_GRACE_LOGINS_REMAINING.get( 401 pwPolicyControl.getWarningValue()); 402 out.println(message); 403 break; 404 } 405 } 406 } 407 } 408 } catch(ClientException ce) 409 { 410 if (debugEnabled()) 411 { 412 TRACER.debugCaught(DebugLogLevel.ERROR, ce); 413 } 414 throw new LDAPConnectionException(ce.getMessageObject(), ce.getExitCode(), 415 null, ce); 416 } catch (LDAPException le) 417 { 418 throw new LDAPConnectionException(le.getMessageObject(), 419 le.getResultCode(), 420 le.getErrorMessage(), 421 le.getMatchedDN(), 422 le.getCause()); 423 } catch(Exception ex) 424 { 425 if (debugEnabled()) 426 { 427 TRACER.debugCaught(DebugLogLevel.ERROR, ex); 428 } 429 throw new LDAPConnectionException( 430 Message.raw(ex.getLocalizedMessage()),ex); 431 } 432 433 } 434 435 /** 436 * Close the underlying ASN1 reader and writer, optionally sending an unbind 437 * request before disconnecting. 438 * 439 * @param nextMessageID The message ID counter that should be used for 440 * the unbind request, or {@code null} if the 441 * connection should be closed without an unbind 442 * request. 443 */ 444 public void close(AtomicInteger nextMessageID) 445 { 446 if(ldapWriter != null) 447 { 448 if (nextMessageID != null) 449 { 450 try 451 { 452 LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(), 453 new UnbindRequestProtocolOp()); 454 ldapWriter.writeMessage(message); 455 } catch (Exception e) {} 456 } 457 458 ldapWriter.close(); 459 } 460 if(ldapReader != null) 461 { 462 ldapReader.close(); 463 } 464 } 465 466 /** 467 * Get the underlying LDAP writer. 468 * 469 * @return The underlying LDAP writer. 470 */ 471 public LDAPWriter getLDAPWriter() 472 { 473 return ldapWriter; 474 } 475 476 /** 477 * Get the underlying LDAP reader. 478 * 479 * @return The underlying LDAP reader. 480 */ 481 public LDAPReader getLDAPReader() 482 { 483 return ldapReader; 484 } 485 486 } 487