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 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.tasks;
028
029import static org.opends.messages.TaskMessages.*;
030import static org.opends.server.util.ServerConstants.*;
031import static org.opends.server.util.StaticUtils.*;
032
033import java.util.List;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.i18n.slf4j.LocalizedLogger;
037import org.forgerock.opendj.ldap.ByteString;
038import org.forgerock.opendj.ldap.ResultCode;
039import org.opends.server.api.ClientConnection;
040import org.opends.server.api.ConnectionHandler;
041import org.opends.server.backends.task.Task;
042import org.opends.server.backends.task.TaskState;
043import org.opends.server.core.DirectoryServer;
044import org.opends.server.types.Attribute;
045import org.opends.server.types.AttributeType;
046import org.opends.server.types.DirectoryException;
047import org.opends.server.types.DisconnectReason;
048import org.opends.server.types.Entry;
049import org.opends.server.types.Operation;
050import org.opends.server.types.Privilege;
051
052/**
053 * This class provides an implementation of a Directory Server task that can be
054 * used to terminate a client connection.
055 */
056public class DisconnectClientTask extends Task
057{
058  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
059
060  /** Indicates whether to send a notification message to the client. */
061  private boolean notifyClient;
062
063  /** The connection ID for the client connection to terminate. */
064  private long connectionID;
065
066  /** The disconnect message to send to the client. */
067  private LocalizableMessage disconnectMessage;
068
069  /** {@inheritDoc} */
070  @Override
071  public LocalizableMessage getDisplayName() {
072    return INFO_TASK_DISCONNECT_CLIENT_NAME.get();
073  }
074
075  /** {@inheritDoc} */
076  @Override
077  public void initializeTask() throws DirectoryException
078  {
079    // If the client connection is available, then make sure the client has the
080    // DISCONNECT_CLIENT privilege.
081    Operation operation = getOperation();
082    if (operation != null)
083    {
084      ClientConnection conn = operation.getClientConnection();
085      if (! conn.hasPrivilege(Privilege.DISCONNECT_CLIENT, operation))
086      {
087        LocalizableMessage message = ERR_TASK_DISCONNECT_NO_PRIVILEGE.get();
088        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
089                                     message);
090      }
091    }
092
093    final Entry taskEntry = getTaskEntry();
094    connectionID = getConnectionID(taskEntry);
095    if (connectionID < 0)
096    {
097      LocalizableMessage message =
098          ERR_TASK_DISCONNECT_NO_CONN_ID.get(ATTR_TASK_DISCONNECT_CONN_ID);
099      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
100    }
101
102    notifyClient = mustNotifyClient(taskEntry);
103    disconnectMessage = getDisconnectMessage(taskEntry);
104  }
105
106  private long getConnectionID(Entry taskEntry) throws DirectoryException
107  {
108    final AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(ATTR_TASK_DISCONNECT_CONN_ID);
109    final List<Attribute> attrList = taskEntry.getAttribute(attrType);
110    if (attrList != null)
111    {
112      for (Attribute a : attrList)
113      {
114        for (ByteString v : a)
115        {
116          try
117          {
118            return Long.parseLong(v.toString());
119          }
120          catch (Exception e)
121          {
122            LocalizableMessage message = ERR_TASK_DISCONNECT_INVALID_CONN_ID.get(v);
123            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e);
124          }
125        }
126      }
127    }
128    return -1;
129  }
130
131  private boolean mustNotifyClient(Entry taskEntry) throws DirectoryException
132  {
133    final AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(ATTR_TASK_DISCONNECT_NOTIFY_CLIENT);
134    final List<Attribute> attrList = taskEntry.getAttribute(attrType);
135    if (attrList != null)
136    {
137      for (Attribute a : attrList)
138      {
139        for (ByteString v : a)
140        {
141          final String stringValue = toLowerCase(v.toString());
142          if ("true".equals(stringValue))
143          {
144            return true;
145          }
146          else if ("false".equals(stringValue))
147          {
148            return false;
149          }
150          else
151          {
152            LocalizableMessage message = ERR_TASK_DISCONNECT_INVALID_NOTIFY_CLIENT.get(stringValue);
153            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
154          }
155        }
156      }
157    }
158    return false;
159  }
160
161  private LocalizableMessage getDisconnectMessage(Entry taskEntry)
162  {
163    AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(ATTR_TASK_DISCONNECT_MESSAGE);
164    List<Attribute> attrList = taskEntry.getAttribute(attrType);
165    if (attrList != null)
166    {
167      for (Attribute a : attrList)
168      {
169        for (ByteString v : a)
170        {
171          return LocalizableMessage.raw(v.toString());
172        }
173      }
174    }
175    return INFO_TASK_DISCONNECT_GENERIC_MESSAGE.get();
176  }
177
178  /** {@inheritDoc} */
179  @Override
180  protected TaskState runTask()
181  {
182    final ClientConnection clientConnection = getClientConnection();
183    if (clientConnection == null)
184    {
185      logger.error(ERR_TASK_DISCONNECT_NO_SUCH_CONNECTION, connectionID);
186      return TaskState.COMPLETED_WITH_ERRORS;
187    }
188
189    clientConnection.disconnect(DisconnectReason.ADMIN_DISCONNECT, notifyClient, disconnectMessage);
190    return TaskState.COMPLETED_SUCCESSFULLY;
191  }
192
193  private ClientConnection getClientConnection()
194  {
195    for (ConnectionHandler<?> handler : DirectoryServer.getConnectionHandlers())
196    {
197      for (ClientConnection c : handler.getClientConnections())
198      {
199        if (c.getConnectionID() == connectionID)
200        {
201          return c;
202        }
203      }
204    }
205    return null;
206  }
207}