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