001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2015 ForgeRock AS. 016 */ 017package org.opends.server.tools; 018import java.io.IOException; 019import java.io.PrintStream; 020import java.net.ConnectException; 021import java.net.InetAddress; 022import java.net.Socket; 023import java.net.SocketException; 024import java.net.UnknownHostException; 025import java.util.ArrayList; 026import java.util.concurrent.atomic.AtomicInteger; 027import java.util.logging.Level; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.opends.server.controls.AuthorizationIdentityResponseControl; 031import org.opends.server.controls.PasswordExpiringControl; 032import org.opends.server.controls.PasswordPolicyErrorType; 033import org.opends.server.controls.PasswordPolicyResponseControl; 034import org.opends.server.controls.PasswordPolicyWarningType; 035import org.opends.server.loggers.JDKLogging; 036import org.forgerock.i18n.slf4j.LocalizedLogger; 037import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp; 038import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp; 039import org.opends.server.protocols.ldap.LDAPControl; 040import org.opends.server.protocols.ldap.LDAPMessage; 041import org.opends.server.protocols.ldap.UnbindRequestProtocolOp; 042import org.forgerock.opendj.ldap.ByteString; 043import org.opends.server.types.Control; 044import org.opends.server.types.DirectoryException; 045import org.opends.server.types.LDAPException; 046 047import com.forgerock.opendj.cli.ClientException; 048import static org.opends.messages.CoreMessages.*; 049import static org.opends.messages.ToolMessages.*; 050import static org.opends.server.protocols.ldap.LDAPResultCode.*; 051import static org.opends.server.util.ServerConstants.*; 052import static org.opends.server.util.StaticUtils.*; 053 054 055 056/** 057 * This class provides a tool that can be used to issue search requests to the 058 * Directory Server. 059 */ 060public class LDAPConnection 061{ 062 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 063 064 /** The hostname to connect to. */ 065 private String hostName; 066 067 /** The port number on which the directory server is accepting requests. */ 068 private int portNumber = 389; 069 070 private LDAPConnectionOptions connectionOptions; 071 private LDAPWriter ldapWriter; 072 private LDAPReader ldapReader; 073 private int versionNumber = 3; 074 075 private final PrintStream out; 076 private final PrintStream err; 077 078 /** 079 * Constructor for the LDAPConnection object. 080 * 081 * @param host The hostname to send the request to. 082 * @param port The port number on which the directory server is accepting 083 * requests. 084 * @param options The set of options for this connection. 085 */ 086 public LDAPConnection(String host, int port, LDAPConnectionOptions options) 087 { 088 this(host, port, options, System.out, System.err); 089 } 090 091 /** 092 * Constructor for the LDAPConnection object. 093 * 094 * @param host The hostname to send the request to. 095 * @param port The port number on which the directory server is accepting 096 * requests. 097 * @param options The set of options for this connection. 098 * @param out The print stream to use for standard output. 099 * @param err The print stream to use for standard error. 100 */ 101 public LDAPConnection(String host, int port, LDAPConnectionOptions options, 102 PrintStream out, PrintStream err) 103 { 104 this.hostName = host; 105 this.portNumber = port; 106 this.connectionOptions = options; 107 this.versionNumber = options.getVersionNumber(); 108 this.out = out; 109 this.err = err; 110 } 111 112 /** 113 * Connects to the directory server instance running on specified hostname 114 * and port number. 115 * 116 * @param bindDN The DN to bind with. 117 * @param bindPassword The password to bind with. 118 * 119 * @throws LDAPConnectionException If a problem occurs while attempting to 120 * establish the connection to the server. 121 */ 122 public void connectToHost(String bindDN, String bindPassword) 123 throws LDAPConnectionException 124 { 125 connectToHost(bindDN, bindPassword, new AtomicInteger(1)); 126 } 127 128 /** 129 * Connects to the directory server instance running on specified hostname 130 * and port number. 131 * 132 * @param bindDN The DN to bind with. 133 * @param bindPassword The password to bind with. 134 * @param nextMessageID The message ID counter that should be used for 135 * operations performed while establishing the 136 * connection. 137 * 138 * @throws LDAPConnectionException If a problem occurs while attempting to 139 * establish the connection to the server. 140 */ 141 public void connectToHost(String bindDN, String bindPassword, 142 AtomicInteger nextMessageID) 143 throws LDAPConnectionException 144 { 145 connectToHost(bindDN, bindPassword, nextMessageID, 0); 146 } 147 148 /** 149 * Connects to the directory server instance running on specified hostname 150 * and port number. 151 * 152 * @param bindDN The DN to bind with. 153 * @param bindPassword The password to bind with. 154 * @param nextMessageID The message ID counter that should be used for 155 * operations performed while establishing the 156 * connection. 157 * @param timeout The timeout to connect to the specified host. The 158 * timeout is the timeout at the socket level in 159 * milliseconds. If the timeout value is {@code 0}, 160 * no timeout is used. 161 * 162 * @throws LDAPConnectionException If a problem occurs while attempting to 163 * establish the connection to the server. 164 */ 165 public void connectToHost(String bindDN, String bindPassword, 166 AtomicInteger nextMessageID, int timeout) 167 throws LDAPConnectionException 168 { 169 Socket socket; 170 Socket startTLSSocket = null; 171 int resultCode; 172 ArrayList<Control> requestControls = new ArrayList<> (); 173 ArrayList<Control> responseControls = new ArrayList<> (); 174 175 if (connectionOptions.isVerbose()) 176 { 177 JDKLogging.enableConsoleLoggingForOpenDJ(Level.ALL); 178 } 179 else 180 { 181 JDKLogging.disableLogging(); 182 } 183 184 185 if(connectionOptions.useStartTLS()) 186 { 187 try 188 { 189 startTLSSocket = createSocket(); 190 ldapWriter = new LDAPWriter(startTLSSocket); 191 ldapReader = new LDAPReader(startTLSSocket); 192 } 193 catch (LDAPConnectionException e) 194 { 195 throw e; 196 } 197 catch (Exception ex) 198 { 199 logger.traceException(ex); 200 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 201 } 202 203 // Send the StartTLS extended request. 204 ExtendedRequestProtocolOp extendedRequest = 205 new ExtendedRequestProtocolOp(OID_START_TLS_REQUEST); 206 207 LDAPMessage msg = new LDAPMessage(nextMessageID.getAndIncrement(), 208 extendedRequest); 209 try 210 { 211 ldapWriter.writeMessage(msg); 212 213 // Read the response from the server. 214 msg = ldapReader.readMessage(); 215 }catch (LDAPException ex1) 216 { 217 logger.traceException(ex1); 218 throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1 219 .getResultCode(), null, ex1); 220 } catch (Exception ex1) 221 { 222 logger.traceException(ex1); 223 throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1); 224 } 225 ExtendedResponseProtocolOp res = msg.getExtendedResponseProtocolOp(); 226 resultCode = res.getResultCode(); 227 if(resultCode != SUCCESS) 228 { 229 throw new LDAPConnectionException(res.getErrorMessage(), 230 resultCode, 231 res.getErrorMessage(), 232 res.getMatchedDN(), null); 233 } 234 } 235 SSLConnectionFactory sslConnectionFactory = 236 connectionOptions.getSSLConnectionFactory(); 237 try 238 { 239 socket = createSSLOrBasicSocket(startTLSSocket, sslConnectionFactory); 240 ldapWriter = new LDAPWriter(socket); 241 ldapReader = new LDAPReader(socket); 242 } catch(UnknownHostException | ConnectException e) 243 { 244 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 245 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, e); 246 } catch (LDAPConnectionException e) 247 { 248 throw e; 249 } catch(Exception ex2) 250 { 251 logger.traceException(ex2); 252 throw new LDAPConnectionException(LocalizableMessage.raw(ex2.getMessage()), ex2); 253 } 254 255 // We need this so that we don't run out of addresses when the tool 256 // commands are called A LOT, as in the unit tests. 257 try 258 { 259 socket.setSoLinger(true, 1); 260 socket.setReuseAddress(true); 261 if (timeout > 0) 262 { 263 socket.setSoTimeout(timeout); 264 } 265 } catch(IOException e) 266 { 267 logger.traceException(e); 268 // It doesn't matter too much if this throws, so ignore it. 269 } 270 271 if (connectionOptions.getReportAuthzID()) 272 { 273 requestControls.add(new LDAPControl(OID_AUTHZID_REQUEST)); 274 } 275 276 if (connectionOptions.usePasswordPolicyControl()) 277 { 278 requestControls.add(new LDAPControl(OID_PASSWORD_POLICY_CONTROL)); 279 } 280 281 LDAPAuthenticationHandler handler = new LDAPAuthenticationHandler( 282 ldapReader, ldapWriter, hostName, nextMessageID); 283 try 284 { 285 ByteString bindDNBytes; 286 if(bindDN == null) 287 { 288 bindDNBytes = ByteString.empty(); 289 } 290 else 291 { 292 bindDNBytes = ByteString.valueOfUtf8(bindDN); 293 } 294 295 ByteString bindPW; 296 if (bindPassword == null) 297 { 298 bindPW = null; 299 } 300 else 301 { 302 bindPW = ByteString.valueOfUtf8(bindPassword); 303 } 304 305 String result = null; 306 if (connectionOptions.useSASLExternal()) 307 { 308 result = handler.doSASLExternal(bindDNBytes, 309 connectionOptions.getSASLProperties(), 310 requestControls, responseControls); 311 } 312 else if (connectionOptions.getSASLMechanism() != null) 313 { 314 result = handler.doSASLBind(bindDNBytes, bindPW, 315 connectionOptions.getSASLMechanism(), 316 connectionOptions.getSASLProperties(), 317 requestControls, responseControls); 318 } 319 else if(bindDN != null) 320 { 321 result = handler.doSimpleBind(versionNumber, bindDNBytes, bindPW, 322 requestControls, responseControls); 323 } 324 if(result != null) 325 { 326 out.println(result); 327 } 328 329 for (Control c : responseControls) 330 { 331 if (c.getOID().equals(OID_AUTHZID_RESPONSE)) 332 { 333 AuthorizationIdentityResponseControl control; 334 if (c instanceof LDAPControl) 335 { 336 // We have to decode this control. 337 control = AuthorizationIdentityResponseControl.DECODER.decode(c 338 .isCritical(), ((LDAPControl) c).getValue()); 339 } 340 else 341 { 342 // Control should already have been decoded. 343 control = (AuthorizationIdentityResponseControl)c; 344 } 345 346 LocalizableMessage message = 347 INFO_BIND_AUTHZID_RETURNED.get( 348 control.getAuthorizationID()); 349 out.println(message); 350 } 351 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED)) 352 { 353 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get(); 354 out.println(message); 355 } 356 else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING)) 357 { 358 PasswordExpiringControl control; 359 if(c instanceof LDAPControl) 360 { 361 // We have to decode this control. 362 control = PasswordExpiringControl.DECODER.decode(c.isCritical(), 363 ((LDAPControl) c).getValue()); 364 } 365 else 366 { 367 // Control should already have been decoded. 368 control = (PasswordExpiringControl)c; 369 } 370 LocalizableMessage timeString = 371 secondsToTimeString(control.getSecondsUntilExpiration()); 372 373 374 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 375 out.println(message); 376 } 377 else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL)) 378 { 379 PasswordPolicyResponseControl pwPolicyControl; 380 if(c instanceof LDAPControl) 381 { 382 pwPolicyControl = PasswordPolicyResponseControl.DECODER.decode(c 383 .isCritical(), ((LDAPControl) c).getValue()); 384 } 385 else 386 { 387 pwPolicyControl = (PasswordPolicyResponseControl)c; 388 } 389 390 391 PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType(); 392 if (errorType != null) 393 { 394 switch (errorType) 395 { 396 case PASSWORD_EXPIRED: 397 398 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get(); 399 out.println(message); 400 break; 401 case ACCOUNT_LOCKED: 402 403 message = INFO_BIND_ACCOUNT_LOCKED.get(); 404 out.println(message); 405 break; 406 case CHANGE_AFTER_RESET: 407 408 message = INFO_BIND_MUST_CHANGE_PASSWORD.get(); 409 out.println(message); 410 break; 411 } 412 } 413 414 PasswordPolicyWarningType warningType = 415 pwPolicyControl.getWarningType(); 416 if (warningType != null) 417 { 418 switch (warningType) 419 { 420 case TIME_BEFORE_EXPIRATION: 421 LocalizableMessage timeString = 422 secondsToTimeString(pwPolicyControl.getWarningValue()); 423 424 425 LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString); 426 out.println(message); 427 break; 428 case GRACE_LOGINS_REMAINING: 429 430 message = INFO_BIND_GRACE_LOGINS_REMAINING.get( 431 pwPolicyControl.getWarningValue()); 432 out.println(message); 433 break; 434 } 435 } 436 } 437 } 438 } catch(ClientException ce) 439 { 440 logger.traceException(ce); 441 throw new LDAPConnectionException(ce.getMessageObject(), ce.getReturnCode(), 442 null, ce); 443 } catch (LDAPException le) { 444 throw new LDAPConnectionException(le.getMessageObject(), 445 le.getResultCode(), 446 le.getErrorMessage(), 447 le.getMatchedDN(), 448 le.getCause()); 449 } catch (DirectoryException de) 450 { 451 throw new LDAPConnectionException(de.getMessageObject(), 452 de.getResultCode().intValue(), null, de.getMatchedDN(), de.getCause()); 453 } catch(Exception ex) 454 { 455 logger.traceException(ex); 456 throw new LDAPConnectionException( 457 LocalizableMessage.raw(ex.getLocalizedMessage()),ex); 458 } 459 finally 460 { 461 if (timeout > 0) 462 { 463 try 464 { 465 socket.setSoTimeout(0); 466 } 467 catch (SocketException e) 468 { 469 e.printStackTrace(); 470 logger.traceException(e); 471 } 472 } 473 } 474 475 } 476 477 /** 478 * Creates a socket using the hostName and portNumber encapsulated in the 479 * current object. For each IP address associated to this host name, 480 * createSocket() will try to open a socket and it will return the first 481 * socket for which we successfully establish a connection. 482 * <p> 483 * This method can never return null because it will receive 484 * UnknownHostException before and then throw LDAPConnectionException. 485 * </p> 486 * 487 * @return a new {@link Socket}. 488 * @throws LDAPConnectionException 489 * if any exception occurs including UnknownHostException 490 */ 491 private Socket createSocket() throws LDAPConnectionException 492 { 493 ConnectException ce = null; 494 try 495 { 496 for (InetAddress inetAddress : InetAddress.getAllByName(hostName)) 497 { 498 try 499 { 500 return new Socket(inetAddress, portNumber); 501 } 502 catch (ConnectException ce2) 503 { 504 if (ce == null) 505 { 506 ce = ce2; 507 } 508 } 509 } 510 } 511 catch (UnknownHostException uhe) 512 { 513 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 514 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 515 uhe); 516 } 517 catch (Exception ex) 518 { 519 // if we get there, something went awfully wrong while creatng one socket, 520 // no need to continue the for loop. 521 logger.traceException(ex); 522 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 523 } 524 if (ce != null) 525 { 526 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 527 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 528 ce); 529 } 530 return null; 531 } 532 533 /** 534 * Creates an SSL socket using the hostName and portNumber encapsulated in the 535 * current object. For each IP address associated to this host name, 536 * createSSLSocket() will try to open a socket and it will return the first 537 * socket for which we successfully establish a connection. 538 * <p> 539 * This method can never return null because it will receive 540 * UnknownHostException before and then throw LDAPConnectionException. 541 * </p> 542 * 543 * @return a new {@link Socket}. 544 * @throws LDAPConnectionException 545 * if any exception occurs including UnknownHostException 546 */ 547 private Socket createSSLSocket(SSLConnectionFactory sslConnectionFactory) 548 throws SSLConnectionException, LDAPConnectionException 549 { 550 ConnectException ce = null; 551 try 552 { 553 for (InetAddress inetAddress : InetAddress.getAllByName(hostName)) 554 { 555 try 556 { 557 return sslConnectionFactory.createSocket(inetAddress, portNumber); 558 } 559 catch (ConnectException ce2) 560 { 561 if (ce == null) 562 { 563 ce = ce2; 564 } 565 } 566 } 567 } 568 catch (UnknownHostException uhe) 569 { 570 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 571 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 572 uhe); 573 } 574 catch (Exception ex) 575 { 576 // if we get there, something went awfully wrong while creatng one socket, 577 // no need to continue the for loop. 578 logger.traceException(ex); 579 throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex); 580 } 581 if (ce != null) 582 { 583 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 584 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 585 ce); 586 } 587 return null; 588 } 589 590 /** 591 * Creates an SSL socket or a normal/basic socket using the hostName and 592 * portNumber encapsulated in the current object, or with the passed in socket 593 * if it needs to use start TLS. 594 * 595 * @param startTLSSocket 596 * the Socket to use if it needs to use start TLS. 597 * @param sslConnectionFactory 598 * the {@link SSLConnectionFactory} for creating SSL sockets 599 * @return a new {@link Socket} 600 * @throws SSLConnectionException 601 * if the SSL socket creation fails 602 * @throws LDAPConnectionException 603 * if any other error occurs 604 */ 605 private Socket createSSLOrBasicSocket(Socket startTLSSocket, 606 SSLConnectionFactory sslConnectionFactory) throws SSLConnectionException, 607 LDAPConnectionException 608 { 609 if (sslConnectionFactory == null) 610 { 611 return createSocket(); 612 } 613 else if (!connectionOptions.useStartTLS()) 614 { 615 return createSSLSocket(sslConnectionFactory); 616 } 617 else 618 { 619 try 620 { 621 // Use existing socket. 622 return sslConnectionFactory.createSocket(startTLSSocket, hostName, 623 portNumber, true); 624 } 625 catch (IOException e) 626 { 627 LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get(); 628 throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null, 629 e); 630 } 631 } 632 } 633 634 /** 635 * Close the underlying ASN1 reader and writer, optionally sending an unbind 636 * request before disconnecting. 637 * 638 * @param nextMessageID The message ID counter that should be used for 639 * the unbind request, or {@code null} if the 640 * connection should be closed without an unbind 641 * request. 642 */ 643 public void close(AtomicInteger nextMessageID) 644 { 645 if(ldapWriter != null) 646 { 647 if (nextMessageID != null) 648 { 649 try 650 { 651 LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(), 652 new UnbindRequestProtocolOp()); 653 ldapWriter.writeMessage(message); 654 } catch (Exception e) {} 655 } 656 657 ldapWriter.close(); 658 } 659 if(ldapReader != null) 660 { 661 ldapReader.close(); 662 } 663 } 664 665 /** 666 * Get the underlying LDAP writer. 667 * 668 * @return The underlying LDAP writer. 669 */ 670 public LDAPWriter getLDAPWriter() 671 { 672 return ldapWriter; 673 } 674 675 /** 676 * Get the underlying LDAP reader. 677 * 678 * @return The underlying LDAP reader. 679 */ 680 public LDAPReader getLDAPReader() 681 { 682 return ldapReader; 683 } 684 685} 686