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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS.
026 */
027package org.opends.server.tools;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.Utils.*;
031
032import static org.opends.messages.ToolMessages.*;
033import static org.opends.server.config.ConfigConstants.*;
034import static org.opends.server.util.CollectionUtils.*;
035import static org.opends.server.protocols.ldap.LDAPResultCode.*;
036import static org.opends.server.util.ServerConstants.*;
037import static org.opends.server.util.StaticUtils.*;
038
039import java.io.IOException;
040import java.io.OutputStream;
041import java.io.PrintStream;
042import java.text.SimpleDateFormat;
043import java.util.ArrayList;
044import java.util.Date;
045import java.util.LinkedList;
046import java.util.TimeZone;
047import java.util.UUID;
048import java.util.concurrent.atomic.AtomicInteger;
049
050import javax.net.ssl.SSLException;
051
052import org.forgerock.i18n.LocalizableMessage;
053import org.forgerock.opendj.ldap.ByteString;
054import org.forgerock.opendj.ldap.DecodeException;
055import org.opends.server.admin.AdministrationConnector;
056import org.opends.server.controls.ProxiedAuthV2Control;
057import org.opends.server.core.DirectoryServer;
058import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
059import org.opends.server.core.LockFileManager;
060import org.opends.server.loggers.JDKLogging;
061import org.opends.server.protocols.ldap.AddRequestProtocolOp;
062import org.opends.server.protocols.ldap.AddResponseProtocolOp;
063import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
064import org.opends.server.protocols.ldap.LDAPAttribute;
065import org.opends.server.protocols.ldap.LDAPConstants;
066import org.opends.server.protocols.ldap.LDAPMessage;
067import org.opends.server.protocols.ldap.LDAPResultCode;
068import org.opends.server.tasks.ShutdownTask;
069import org.opends.server.tools.tasks.TaskTool;
070import org.opends.server.types.Control;
071import org.opends.server.types.LDAPException;
072import org.opends.server.types.NullOutputStream;
073import org.opends.server.types.RawAttribute;
074import org.opends.server.util.args.LDAPConnectionArgumentParser;
075
076import com.forgerock.opendj.cli.Argument;
077import com.forgerock.opendj.cli.ArgumentConstants;
078import com.forgerock.opendj.cli.ArgumentException;
079import com.forgerock.opendj.cli.ArgumentParser;
080import com.forgerock.opendj.cli.BooleanArgument;
081import com.forgerock.opendj.cli.CommonArguments;
082import com.forgerock.opendj.cli.FileBasedArgument;
083import com.forgerock.opendj.cli.IntegerArgument;
084import com.forgerock.opendj.cli.StringArgument;
085
086/**
087 * This class provides a tool that can send a request to the Directory Server
088 * that will cause it to shut down.
089 */
090public class StopDS
091{
092  /** The fully-qualified name of this class. */
093  private static final String CLASS_NAME = "org.opends.server.tools.StopDS";
094
095  /**
096   * Return codes used when the hidden option --checkStoppability is used.
097   * NOTE: when checkStoppability is specified is recommended not to allocate
098   * a lot of memory for the JVM (Using -Xms and -Xmx options) as there might
099   * be calls to Runtime.exec.
100   */
101  /** The server is already stopped. */
102  private static int SERVER_ALREADY_STOPPED = 98;
103  /** The server must be started. */
104  private static int START_SERVER = 99;
105  /** The server must be stopped using a system call. */
106  private static int STOP_USING_SYSTEM_CALL = 100;
107  /** The server must be restarted using system calls. */
108  private static int RESTART_USING_SYSTEM_CALL = 101;
109  /** The server must be stopped using protocol. */
110  private static int STOP_USING_PROTOCOL = 102;
111  /** The server must be stopped as a window service. */
112  private static int STOP_AS_WINDOW_SERVICE = 103;
113  /** The server must be restarted as a window service. */
114  private static int RESTART_AS_WINDOW_SERVICE = 104;
115  /** The server must be started and it should use quiet mode. */
116  private static int START_SERVER_QUIET = 105;
117  /** The server must be restarted using system calls and it should use quiet mode. */
118  private static int RESTART_USING_SYSTEM_CALL_QUIET = 106;
119
120  /**
121   * Invokes the <CODE>stopDS</CODE> method, passing it the provided command
122   * line arguments.  If the call to <CODE>stopDS</CODE> returns a nonzero
123   * value, then that will be used as the exit code for this program.
124   *
125   * @param  args  The command-line arguments provided to this program.
126   */
127  public static void main(String[] args)
128  {
129    int result = stopDS(args, System.out, System.err);
130
131    if (result != LDAPResultCode.SUCCESS)
132    {
133      System.exit(filterExitCode(result));
134    }
135  }
136
137
138
139  /**
140   * Parses the provided set of command-line arguments and attempts to contact
141   * the Directory Server in order to send it the shutdown request.
142   *
143   * @param  args  The command-line arguments provided to this program.
144   *
145   * @return  An integer value that indicates whether the shutdown request was
146   *          accepted by the Directory Server.  A nonzero value should be
147   *          interpreted as a failure of some kind.
148   */
149  public static int stopDS(String[] args)
150  {
151    return stopDS(args, System.out, System.err);
152  }
153
154
155
156  /**
157   * Parses the provided set of command-line arguments and attempts to contact
158   * the Directory Server in order to send it the shutdown request.
159   *
160   * @param  args       The command-line arguments provided to this program.
161   * @param  outStream  The output stream to use for standard output, or
162   *                    <CODE>null</CODE> if standard output is not needed.
163   * @param  errStream  The output stream to use for standard error, or
164   *                    <CODE>null</CODE> if standard error is not needed.
165   *
166   * @return  An integer value that indicates whether the shutdown request was
167   *          accepted by the Directory Server.  A nonzero value should be
168   *          interpreted as a failure of some kind.
169   */
170  public static int stopDS(String[] args, OutputStream outStream,
171                           OutputStream errStream)
172  {
173    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
174    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
175    JDKLogging.disableLogging();
176
177    // Define all the arguments that may be used with this program.
178    LocalizableMessage toolDescription = INFO_STOPDS_TOOL_DESCRIPTION.get();
179    ArgumentParser    argParser = new ArgumentParser(CLASS_NAME,
180                                                     toolDescription, false);
181    argParser.setShortToolDescription(REF_SHORT_DESC_STOP_DS.get());
182
183    argParser.setVersionHandler(new DirectoryServerVersionHandler());
184    BooleanArgument   checkStoppability;
185    BooleanArgument   quietMode;
186    BooleanArgument   windowsNetStop;
187    BooleanArgument   restart;
188    BooleanArgument   showUsage;
189    BooleanArgument   trustAll;
190    FileBasedArgument bindPWFile;
191    FileBasedArgument keyStorePWFile;
192    FileBasedArgument trustStorePWFile;
193    IntegerArgument   port;
194    StringArgument    bindDN;
195    StringArgument    bindPW;
196    StringArgument    certNickname;
197    StringArgument    host;
198    StringArgument    keyStoreFile;
199    StringArgument    keyStorePW;
200    StringArgument    proxyAuthzID;
201    StringArgument    saslOption;
202    StringArgument    stopReason;
203    StringArgument    stopTimeStr;
204    StringArgument    trustStoreFile;
205    StringArgument    trustStorePW;
206    StringArgument    propertiesFileArgument;
207    BooleanArgument   noPropertiesFileArgument;
208
209    try
210    {
211      propertiesFileArgument = new StringArgument("propertiesFilePath",
212          null, OPTION_LONG_PROP_FILE_PATH,
213          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
214          INFO_DESCRIPTION_PROP_FILE_PATH.get());
215      argParser.addArgument(propertiesFileArgument);
216      argParser.setFilePropertiesArgument(propertiesFileArgument);
217
218      noPropertiesFileArgument = new BooleanArgument(
219          "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
220          INFO_DESCRIPTION_NO_PROP_FILE.get());
221      argParser.addArgument(noPropertiesFileArgument);
222      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
223
224      host = new StringArgument("host", OPTION_SHORT_HOST,
225                                OPTION_LONG_HOST, false, false, true,
226                                INFO_HOST_PLACEHOLDER.get(), "127.0.0.1", null,
227                                INFO_STOPDS_DESCRIPTION_HOST.get());
228      host.setPropertyName(OPTION_LONG_HOST);
229      argParser.addArgument(host);
230
231      port = new IntegerArgument(
232              "port", OPTION_SHORT_PORT,
233              OPTION_LONG_PORT, false, false, true,
234              INFO_PORT_PLACEHOLDER.get(),
235              AdministrationConnector.DEFAULT_ADMINISTRATION_CONNECTOR_PORT,
236              null, true, 1,
237              true, 65535, INFO_STOPDS_DESCRIPTION_PORT.get());
238      port.setPropertyName(OPTION_LONG_PORT);
239      argParser.addArgument(port);
240
241      bindDN = new StringArgument("binddn", OPTION_SHORT_BINDDN,
242                                  OPTION_LONG_BINDDN, false, false, true,
243                                  INFO_BINDDN_PLACEHOLDER.get(), null, null,
244                                  INFO_STOPDS_DESCRIPTION_BINDDN.get());
245      bindDN.setPropertyName(OPTION_LONG_BINDDN);
246      argParser.addArgument(bindDN);
247
248      bindPW = new StringArgument("bindpw", OPTION_SHORT_BINDPWD,
249                                  OPTION_LONG_BINDPWD, false, false,
250                                  true,
251                                  INFO_BINDPWD_PLACEHOLDER.get(), null, null,
252                                  INFO_STOPDS_DESCRIPTION_BINDPW.get());
253      bindPW.setPropertyName(OPTION_LONG_BINDPWD);
254      argParser.addArgument(bindPW);
255
256      bindPWFile = new FileBasedArgument(
257              "bindpwfile",
258              OPTION_SHORT_BINDPWD_FILE,
259              OPTION_LONG_BINDPWD_FILE,
260              false, false,
261              INFO_BINDPWD_FILE_PLACEHOLDER.get(),
262              null, null,
263              INFO_STOPDS_DESCRIPTION_BINDPWFILE.get());
264      bindPWFile.setPropertyName(OPTION_LONG_BINDPWD_FILE);
265      argParser.addArgument(bindPWFile);
266
267      saslOption = new StringArgument(
268              "sasloption", OPTION_SHORT_SASLOPTION,
269              OPTION_LONG_SASLOPTION, false,
270              true, true,
271              INFO_SASL_OPTION_PLACEHOLDER.get(), null, null,
272              INFO_STOPDS_DESCRIPTION_SASLOPTIONS.get());
273      saslOption.setPropertyName(OPTION_LONG_SASLOPTION);
274      argParser.addArgument(saslOption);
275
276      proxyAuthzID = new StringArgument(
277              "proxyauthzid",
278              OPTION_SHORT_PROXYAUTHID,
279              OPTION_LONG_PROXYAUTHID, false,
280              false, true,
281              INFO_PROXYAUTHID_PLACEHOLDER.get(), null,
282              null,
283              INFO_STOPDS_DESCRIPTION_PROXYAUTHZID.get());
284      proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
285      argParser.addArgument(proxyAuthzID);
286
287      stopReason = new StringArgument(
288              "stopreason", 'r', "stopReason", false,
289              false, true, INFO_STOP_REASON_PLACEHOLDER.get(), null, null,
290              INFO_STOPDS_DESCRIPTION_STOP_REASON.get());
291      stopReason.setPropertyName("stopReason");
292      argParser.addArgument(stopReason);
293
294      checkStoppability = new BooleanArgument("checkstoppability", null,
295              "checkStoppability",
296              INFO_STOPDS_CHECK_STOPPABILITY.get());
297      checkStoppability.setHidden(true);
298      argParser.addArgument(checkStoppability);
299
300      windowsNetStop = new BooleanArgument("windowsnetstop", null,
301          "windowsNetStop", INFO_STOPDS_DESCRIPTION_WINDOWS_NET_STOP.get());
302      windowsNetStop.setHidden(true);
303      argParser.addArgument(windowsNetStop);
304
305      restart = CommonArguments.getRestart();
306      argParser.addArgument(restart);
307
308      stopTimeStr = new StringArgument("stoptime", 't', "stopTime", false,
309                                       false, true,
310                                       INFO_STOP_TIME_PLACEHOLDER.get(), null,
311                                       null,
312                                       INFO_STOPDS_DESCRIPTION_STOP_TIME.get());
313      stopTimeStr.setPropertyName("stopTime");
314      argParser.addArgument(stopTimeStr);
315
316      trustAll = CommonArguments.getTrustAll();
317      argParser.addArgument(trustAll);
318
319      keyStoreFile = new StringArgument("keystorefile",
320                                        OPTION_SHORT_KEYSTOREPATH,
321                                        OPTION_LONG_KEYSTOREPATH,
322                                        false, false, true,
323                                        INFO_KEYSTOREPATH_PLACEHOLDER.get(),
324                                        null, null,
325                                        INFO_STOPDS_DESCRIPTION_KSFILE.get());
326      keyStoreFile.setPropertyName(OPTION_LONG_KEYSTOREPATH);
327      argParser.addArgument(keyStoreFile);
328
329      keyStorePW = new StringArgument("keystorepw", OPTION_SHORT_KEYSTORE_PWD,
330                                      OPTION_LONG_KEYSTORE_PWD,
331                                      false, false, true,
332                                      INFO_KEYSTORE_PWD_PLACEHOLDER.get(),
333                                      null, null,
334                                      INFO_STOPDS_DESCRIPTION_KSPW.get());
335      keyStorePW.setPropertyName(OPTION_LONG_KEYSTORE_PWD);
336      argParser.addArgument(keyStorePW);
337
338      keyStorePWFile = new FileBasedArgument(
339              "keystorepwfile",
340              OPTION_SHORT_KEYSTORE_PWD_FILE,
341              OPTION_LONG_KEYSTORE_PWD_FILE,
342              false, false,
343              INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get(),
344              null, null,
345              INFO_STOPDS_DESCRIPTION_KSPWFILE.get());
346      keyStorePWFile.setPropertyName(OPTION_LONG_KEYSTORE_PWD_FILE);
347      argParser.addArgument(keyStorePWFile);
348
349      certNickname = new StringArgument(
350              "certnickname", 'N', "certNickname",
351              false, false, true, INFO_NICKNAME_PLACEHOLDER.get(), null,
352              null, INFO_DESCRIPTION_CERT_NICKNAME.get());
353      certNickname.setPropertyName("certNickname");
354      argParser.addArgument(certNickname);
355
356      trustStoreFile = new StringArgument("truststorefile",
357                                          OPTION_SHORT_TRUSTSTOREPATH,
358                                          OPTION_LONG_TRUSTSTOREPATH,
359                                          false, false, true,
360                                          INFO_TRUSTSTOREPATH_PLACEHOLDER.get(),
361                                          null, null,
362                                          INFO_STOPDS_DESCRIPTION_TSFILE.get());
363      trustStoreFile.setPropertyName(OPTION_LONG_TRUSTSTOREPATH);
364      argParser.addArgument(trustStoreFile);
365
366      trustStorePW = new StringArgument(
367              "truststorepw", 'T',
368              OPTION_LONG_TRUSTSTORE_PWD,
369              false, false,
370              true, INFO_TRUSTSTORE_PWD_PLACEHOLDER.get(), null,
371              null, INFO_STOPDS_DESCRIPTION_TSPW.get());
372      trustStorePW.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD);
373      argParser.addArgument(trustStorePW);
374
375      trustStorePWFile = new FileBasedArgument("truststorepwfile",
376                                  OPTION_SHORT_TRUSTSTORE_PWD_FILE,
377                                  OPTION_LONG_TRUSTSTORE_PWD_FILE,
378                                  false, false,
379                                  INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get(),
380                                  null, null,
381                                  INFO_STOPDS_DESCRIPTION_TSPWFILE.get());
382      trustStorePWFile.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD_FILE);
383      argParser.addArgument(trustStorePWFile);
384
385      quietMode = CommonArguments.getQuiet();
386      argParser.addArgument(quietMode);
387
388      showUsage = CommonArguments.getShowUsage();
389      argParser.addArgument(showUsage);
390      argParser.setUsageArgument(showUsage, out);
391    }
392    catch (ArgumentException ae)
393    {
394      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
395      return CLIENT_SIDE_PARAM_ERROR;
396    }
397
398
399    // Parse the command-line arguments provided to the program.
400    try
401    {
402      argParser.parseArguments(args);
403    }
404    catch (ArgumentException ae)
405    {
406      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
407      return CLIENT_SIDE_PARAM_ERROR;
408    }
409
410
411    // If we should just display usage or version information,
412    // then exit because it will have already been done.
413    if (argParser.usageOrVersionDisplayed())
414    {
415      return LDAPResultCode.SUCCESS;
416    }
417
418    if (quietMode.isPresent())
419    {
420      out = NullOutputStream.printStream();
421    }
422
423    if (checkStoppability.isPresent())
424    {
425      System.exit(checkStoppability(argParser, out, err));
426    }
427
428    // If both a bind password and bind password file were provided, then return
429    // an error.
430    if (bindPW.isPresent() && bindPWFile.isPresent())
431    {
432      printWrappedText(err,
433          ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(bindPW.getLongIdentifier(), bindPWFile.getLongIdentifier()));
434      return CLIENT_SIDE_PARAM_ERROR;
435    }
436
437
438    // If both a key store password and key store password file were provided,
439    // then return an error.
440    if (keyStorePW.isPresent() && keyStorePWFile.isPresent())
441    {
442      printWrappedText(err, ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
443              keyStorePW.getLongIdentifier(), keyStorePWFile.getLongIdentifier()));
444      return CLIENT_SIDE_PARAM_ERROR;
445    }
446
447
448    // If both a trust store password and trust store password file were
449    // provided, then return an error.
450    if (trustStorePW.isPresent() && trustStorePWFile.isPresent())
451    {
452      printWrappedText(err, ERR_STOPDS_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
453              trustStorePW.getLongIdentifier(), trustStorePWFile.getLongIdentifier()));
454      return CLIENT_SIDE_PARAM_ERROR;
455    }
456
457
458    // Make sure that we can decode the stop time string if one was provided.
459    Date stopTime = new Date();
460    if (stopTimeStr.isPresent())
461    {
462      String timeStr = stopTimeStr.getValue();
463      if (!TaskTool.NOW.equals(timeStr))
464      {
465        try
466        {
467          stopTime = parseDateTimeString(timeStr);
468        }
469        catch (Exception e)
470        {
471          printWrappedText(err, ERR_STOPDS_CANNOT_DECODE_STOP_TIME.get());
472          return CLIENT_SIDE_PARAM_ERROR;
473        }
474        // Check that the provided date is not previous to the current date.
475        Date currentDate = new Date(System.currentTimeMillis());
476        if (currentDate.after(stopTime))
477        {
478          printWrappedText(err, ERR_STOPDS_DATETIME_ALREADY_PASSED.get(timeStr));
479          return CLIENT_SIDE_PARAM_ERROR;
480        }
481      }
482    }
483
484
485    // Create the LDAP connection options object, which will be used to
486    // customize the way that we connect to the server and specify a set of
487    // basic defaults.
488    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
489    connectionOptions.setVersionNumber(3);
490
491
492    try {
493      String clientAlias;
494      if (certNickname.isPresent()) {
495        clientAlias = certNickname.getValue();
496      } else {
497        clientAlias = null;
498      }
499
500      SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
501      sslConnectionFactory.init(trustAll.isPresent(), keyStoreFile.getValue(),
502        keyStorePW.getValue(), clientAlias,
503        trustStoreFile.getValue(),
504        trustStorePW.getValue());
505
506      connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
507    } catch (SSLConnectionException sce) {
508      printWrappedText(err, ERR_STOPDS_CANNOT_INITIALIZE_SSL.get(sce.getMessage()));
509      return CLIENT_SIDE_LOCAL_ERROR;
510    }
511
512
513    // If one or more SASL options were provided, then make sure that one of
514    // them was "mech" and specified a valid SASL mechanism.
515    if (saslOption.isPresent())
516    {
517      String             mechanism = null;
518      LinkedList<String> options   = new LinkedList<>();
519
520      for (String s : saslOption.getValues())
521      {
522        int equalPos = s.indexOf('=');
523        if (equalPos <= 0)
524        {
525          printWrappedText(err, ERR_STOPDS_CANNOT_PARSE_SASL_OPTION.get(s));
526          return CLIENT_SIDE_PARAM_ERROR;
527        }
528        else
529        {
530          String name  = s.substring(0, equalPos);
531
532          if (name.equalsIgnoreCase("mech"))
533          {
534            mechanism = s;
535          }
536          else
537          {
538            options.add(s);
539          }
540        }
541      }
542
543      if (mechanism == null)
544      {
545        printWrappedText(err, ERR_STOPDS_NO_SASL_MECHANISM.get());
546        return CLIENT_SIDE_PARAM_ERROR;
547      }
548
549      connectionOptions.setSASLMechanism(mechanism);
550
551      for (String option : options)
552      {
553        connectionOptions.addSASLProperty(option);
554      }
555    }
556
557
558    // Attempt to connect and authenticate to the Directory Server.
559    AtomicInteger nextMessageID = new AtomicInteger(1);
560    LDAPConnection connection;
561    try
562    {
563      connection = new LDAPConnection(host.getValue(), port.getIntValue(),
564                                      connectionOptions, out, err);
565      connection.connectToHost(bindDN.getValue(),
566          LDAPConnectionArgumentParser.getPasswordValue(bindPW, bindPWFile,
567                                                        bindDN, out, err),
568          nextMessageID);
569    }
570    catch (ArgumentException ae)
571    {
572      argParser.displayMessageAndUsageReference(
573          err, ERR_STOPDS_CANNOT_DETERMINE_PORT.get(port.getLongIdentifier(), ae.getMessage()));
574      return CLIENT_SIDE_PARAM_ERROR;
575    }
576    catch (LDAPConnectionException lce)
577    {
578      LocalizableMessage message = null;
579      if (lce.getCause() != null && lce.getCause().getCause() != null &&
580        lce.getCause().getCause() instanceof SSLException) {
581      message = ERR_STOPDS_CANNOT_CONNECT_SSL.get(host.getValue(),
582        port.getValue());
583      } else {
584        String hostPort = host.getValue() + ":" + port.getValue();
585        message = ERR_STOPDS_CANNOT_CONNECT.get(hostPort,
586          lce.getMessage());
587      }
588      printWrappedText(err, message);
589      return CLIENT_SIDE_CONNECT_ERROR;
590    }
591
592    LDAPReader reader = connection.getLDAPReader();
593    LDAPWriter writer = connection.getLDAPWriter();
594
595
596    // Construct the add request to send to the server.
597    String taskID = UUID.randomUUID().toString();
598    ByteString entryDN =
599        ByteString.valueOfUtf8(ATTR_TASK_ID + "=" + taskID + "," +
600                            SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT);
601
602    ArrayList<RawAttribute> attributes = new ArrayList<>();
603    attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, newArrayList("top", "ds-task", "ds-task-shutdown")));
604    attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskID));
605    attributes.add(new LDAPAttribute(ATTR_TASK_CLASS, ShutdownTask.class.getName()));
606    if (restart.isPresent())
607    {
608      attributes.add(new LDAPAttribute(ATTR_RESTART_SERVER, "true"));
609    }
610    if (stopReason.isPresent())
611    {
612      attributes.add(new LDAPAttribute(ATTR_SHUTDOWN_MESSAGE, stopReason.getValue()));
613    }
614
615    if (stopTime != null)
616    {
617      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
618      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
619      String stopTimeValues = dateFormat.format(stopTime);
620      attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME, stopTimeValues));
621    }
622
623    ArrayList<Control> controls = new ArrayList<>();
624    if (proxyAuthzID.isPresent())
625    {
626      controls.add(new ProxiedAuthV2Control(
627          ByteString.valueOfUtf8(proxyAuthzID.getValue())));
628    }
629
630    AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN, attributes);
631    LDAPMessage requestMessage =
632         new LDAPMessage(nextMessageID.getAndIncrement(), addRequest, controls);
633
634
635    // Send the request to the server and read the response.
636    LDAPMessage responseMessage;
637    try
638    {
639      writer.writeMessage(requestMessage);
640
641      responseMessage = reader.readMessage();
642      if (responseMessage == null)
643      {
644        printWrappedText(err, ERR_STOPDS_UNEXPECTED_CONNECTION_CLOSURE.get());
645        return CLIENT_SIDE_SERVER_DOWN;
646      }
647    }
648    catch (DecodeException | LDAPException e)
649    {
650      printWrappedText(err, ERR_STOPDS_DECODE_ERROR.get(e.getMessage()));
651      return CLIENT_SIDE_DECODING_ERROR;
652    }
653    catch (IOException ioe)
654    {
655      printWrappedText(err, ERR_STOPDS_IO_ERROR.get(ioe));
656      return LDAPResultCode.CLIENT_SIDE_SERVER_DOWN;
657    }
658
659
660    if (responseMessage.getProtocolOpType() !=
661        LDAPConstants.OP_TYPE_ADD_RESPONSE)
662    {
663      if (responseMessage.getProtocolOpType() ==
664          LDAPConstants.OP_TYPE_EXTENDED_RESPONSE)
665      {
666        // It's possible that this is a notice of disconnection, which we can
667        // probably interpret as a "success" in this case.
668        ExtendedResponseProtocolOp extendedResponse =
669             responseMessage.getExtendedResponseProtocolOp();
670        String responseOID = extendedResponse.getOID();
671        if (LDAPConstants.OID_NOTICE_OF_DISCONNECTION.equals(responseOID))
672        {
673          printWrappedText(err, extendedResponse.getErrorMessage());
674          return extendedResponse.getResultCode();
675        }
676      }
677
678
679      printWrappedText(err, ERR_STOPDS_INVALID_RESPONSE_TYPE.get(responseMessage.getProtocolOpName()));
680
681
682      return CLIENT_SIDE_LOCAL_ERROR;
683    }
684
685
686    AddResponseProtocolOp addResponse = responseMessage.getAddResponseProtocolOp();
687    printWrappedText(err, addResponse.getErrorMessage());
688    return addResponse.getResultCode();
689  }
690
691  /**
692   * Returns the error code that we return when we are checking the stoppability
693   * of the server.  This basically tells the invoker what must be done based
694   * on the different parameters passed.
695   * @param argParser the ArgumentParser with the arguments already parsed.
696   * @param out the print stream to use for standard output.
697   * @param err the print stream to use for standard error.
698   * @return the error code that we return when we are checking the stoppability
699   * of the server.
700   */
701  private static int checkStoppability(ArgumentParser argParser,
702                                       PrintStream out, PrintStream err)
703  {
704    int returnValue;
705    boolean isServerRunning;
706
707    boolean quietMode = false;
708    Argument quietArg = argParser.getArgumentForLongID(ArgumentConstants.OPTION_LONG_QUIET);
709    if (quietArg != null && quietArg.isPresent())
710    {
711      quietMode = true;
712    }
713
714    BooleanArgument restart =
715      (BooleanArgument)argParser.getArgumentForLongID(OPTION_LONG_RESTART);
716    boolean restartPresent = restart.isPresent();
717    BooleanArgument windowsNetStop =
718      (BooleanArgument)argParser.getArgumentForLongID("windowsnetstop");
719    boolean windowsNetStopPresent = windowsNetStop.isPresent();
720
721    // Check if this is a stop through protocol.
722    LinkedList<Argument> list = argParser.getArgumentList();
723    boolean stopThroughProtocol = false;
724    for (Argument arg: list)
725    {
726      if (!OPTION_LONG_RESTART.toLowerCase().equals(arg.getName()) &&
727          !OPTION_LONG_QUIET.equals(arg.getName()) &&
728          !OPTION_LONG_HELP.toLowerCase().equals(arg.getName()) &&
729          !"checkstoppability".equals(arg.getName()) &&
730          !"windowsnetstop".equals(arg.getName()) &&
731          ! OPTION_LONG_NO_PROP_FILE.equals(arg.getLongIdentifier()))
732      {
733        stopThroughProtocol |= arg.isPresent();
734      }
735    }
736
737    if (stopThroughProtocol)
738    {
739      // Assume that this is done on a remote server and do no more checks.
740      returnValue = STOP_USING_PROTOCOL;
741    }
742    else
743    {
744      String lockFile = LockFileManager.getServerLockFileName();
745      try
746      {
747        StringBuilder failureReason = new StringBuilder();
748        if (LockFileManager.acquireExclusiveLock(lockFile, failureReason))
749        {
750          // The server is not running: write a message informing of that
751          // in the standard out (this is not an error message).
752          LocalizableMessage message = INFO_STOPDS_SERVER_ALREADY_STOPPED.get();
753          out.println(message);
754          LockFileManager.releaseLock(lockFile, failureReason);
755          isServerRunning = false;
756        }
757        else
758        {
759          isServerRunning = true;
760        }
761      }
762      catch (Exception e)
763      {
764        // Assume that if we cannot acquire the lock file the server is
765        // running.
766        isServerRunning = true;
767      }
768
769      boolean configuredAsService =
770          DirectoryServer.isRunningAsWindowsService();
771
772      if (!isServerRunning)
773      {
774        if (configuredAsService && !windowsNetStopPresent)
775        {
776          if (restartPresent)
777          {
778            returnValue = RESTART_AS_WINDOW_SERVICE;
779          }
780          else
781          {
782            returnValue = STOP_AS_WINDOW_SERVICE;
783          }
784        }
785        else if (restartPresent)
786        {
787          if (quietMode)
788          {
789            returnValue = START_SERVER_QUIET;
790          }
791          else
792          {
793            returnValue = START_SERVER;
794          }
795        }
796        else
797        {
798          returnValue = SERVER_ALREADY_STOPPED;
799        }
800      }
801      else
802      {
803        if (configuredAsService)
804        {
805          if (windowsNetStopPresent)
806          {
807            // stop-ds.bat is being called through net stop, so return
808            // STOP_USING_SYSTEM_CALL or RESTART_USING_SYSTEM_CALL so that the
809            // batch file actually stops the server.
810            if (restartPresent)
811            {
812              if (quietMode)
813              {
814                returnValue = RESTART_USING_SYSTEM_CALL_QUIET;
815              }
816              else
817              {
818                returnValue = RESTART_USING_SYSTEM_CALL;
819              }
820            }
821            else
822            {
823              returnValue = STOP_USING_SYSTEM_CALL;
824            }
825          }
826          else
827          {
828            if (restartPresent)
829            {
830              returnValue = RESTART_AS_WINDOW_SERVICE;
831            }
832            else
833            {
834              returnValue = STOP_AS_WINDOW_SERVICE;
835            }
836            // Display a message informing that we are going to the server.
837
838            LocalizableMessage message = INFO_STOPDS_GOING_TO_STOP.get();
839            out.println(message);
840          }
841        }
842        else
843        {
844          // Display a message informing that we are going to the server.
845
846          LocalizableMessage message = INFO_STOPDS_GOING_TO_STOP.get();
847          out.println(message);
848
849          if (restartPresent)
850          {
851            if (quietMode)
852            {
853              returnValue = RESTART_USING_SYSTEM_CALL_QUIET;
854            }
855            else
856            {
857              returnValue = RESTART_USING_SYSTEM_CALL;
858            }
859          }
860          else
861          {
862            returnValue = STOP_USING_SYSTEM_CALL;
863          }
864        }
865      }
866    }
867    return returnValue;
868  }
869}
870