blob: 8aba389224a1a56823cd52defb047e84030cba29 [file] [log] [blame]
Felix Meschbergerefb2d082008-08-19 13:18:47 +00001/*
Richard S. Hall59aef192009-04-25 14:50:37 +00002 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Felix Meschbergerefb2d082008-08-19 13:18:47 +000017package org.apache.felix.shell.remote;
18
Felix Meschbergerefb2d082008-08-19 13:18:47 +000019import java.io.IOException;
20import java.io.PrintStream;
Felix Meschberger35901032009-01-30 22:43:37 +000021import java.net.InetAddress;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000022import java.net.ServerSocket;
23import java.net.Socket;
24import java.net.SocketException;
Felix Meschberger51825702009-02-02 07:07:38 +000025import java.net.SocketTimeoutException;
Felix Meschberger73bf1232009-02-02 08:28:42 +000026import java.util.HashSet;
27import java.util.Set;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000028
Felix Meschbergerd7c324d2008-08-19 14:10:48 +000029import org.osgi.framework.BundleContext;
30
Felix Meschbergerefb2d082008-08-19 13:18:47 +000031/**
32 * Implements a simple listener that will accept a single connection.
Felix Meschbergerefb2d082008-08-19 13:18:47 +000033 */
34class Listener
35{
Richard S. Hall55900ec2009-04-25 16:57:58 +000036 private final int m_port;
37 private final String m_ip;
38 private final Thread m_listenerThread;
39 private final Acceptor m_acceptor;
40 private final AtomicInteger m_useCounter;
41 private final int m_maxConnections;
42 private final int m_soTimeout;
43 private final Set m_connections;
44 private final ServiceMediator m_services;
Felix Meschbergerefb2d082008-08-19 13:18:47 +000045
Felix Meschbergerefb2d082008-08-19 13:18:47 +000046 /**
47 * Activates this listener on a listener thread (telnetconsole.Listener).
48 */
Richard S. Hall55900ec2009-04-25 16:57:58 +000049 public Listener(BundleContext context, ServiceMediator services) throws IOException
Felix Meschbergerefb2d082008-08-19 13:18:47 +000050 {
Richard S. Hall55900ec2009-04-25 16:57:58 +000051 m_services = services;
Felix Meschbergerd7c324d2008-08-19 14:10:48 +000052 //configure from framework property
Richard S. Hall55900ec2009-04-25 16:57:58 +000053 m_ip = getProperty(context, "osgi.shell.telnet.ip", "127.0.0.1");
54 m_port = getProperty(context, "osgi.shell.telnet.port", 6666);
55 m_soTimeout = getProperty(context, "osgi.shell.telnet.socketTimeout", 0);
56 m_maxConnections = getProperty(context, "osgi.shell.telnet.maxconn", 2);
Richard S. Hall59aef192009-04-25 14:50:37 +000057 m_useCounter = new AtomicInteger(0);
Felix Meschberger73bf1232009-02-02 08:28:42 +000058 m_connections = new HashSet();
Richard S. Hall55900ec2009-04-25 16:57:58 +000059 m_acceptor = new Acceptor();
60 m_listenerThread = new Thread(m_acceptor, "telnetconsole.Listener");
Richard S. Hall59aef192009-04-25 14:50:37 +000061 m_listenerThread.start();
Felix Meschbergerefb2d082008-08-19 13:18:47 +000062 }//activate
Richard S. Hall55900ec2009-04-25 16:57:58 +000063 public ServiceMediator getServices()
64 {
65 return m_services;
66 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +000067
Felix Meschbergerefb2d082008-08-19 13:18:47 +000068 /**
69 * Deactivates this listener.
70 * <p/>
71 * The listener's socket will be closed, which should cause an interrupt in the
72 * listener thread and allow for it to return. The calling thread joins the listener
73 * thread until it returns (to ensure a clean stop).
74 */
75 public void deactivate()
76 {
77 try
78 {
Felix Meschbergerefb2d082008-08-19 13:18:47 +000079 //wait for the listener thread
Richard S. Hall55900ec2009-04-25 16:57:58 +000080 m_acceptor.close();
Richard S. Hall59aef192009-04-25 14:50:37 +000081 m_listenerThread.join();
Felix Meschbergerefb2d082008-08-19 13:18:47 +000082 }
Richard S. Hall59aef192009-04-25 14:50:37 +000083 catch (Exception ex)
Felix Meschbergerefb2d082008-08-19 13:18:47 +000084 {
Richard S. Hall55900ec2009-04-25 16:57:58 +000085 m_services.error("Listener::deactivate()", ex);
Felix Meschbergerefb2d082008-08-19 13:18:47 +000086 }
Richard S. Hall59aef192009-04-25 14:50:37 +000087
Felix Meschberger73bf1232009-02-02 08:28:42 +000088 // get the active connections (and clear the list)
89 // we have to work on a copy, since stopping any active connection
90 // will try to remove itself from the set, which might cause a
91 // ConcurrentModificationException if we would iterate over the list
92 Shell[] connections;
Richard S. Hall59aef192009-04-25 14:50:37 +000093 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +000094 {
Richard S. Hall59aef192009-04-25 14:50:37 +000095 connections = (Shell[]) m_connections.toArray(new Shell[m_connections.size()]);
Felix Meschberger73bf1232009-02-02 08:28:42 +000096 m_connections.clear();
97 }
98
99 // now terminate all active connections
Richard S. Hall59aef192009-04-25 14:50:37 +0000100 for (int i = 0; i < connections.length; i++)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000101 {
102 connections[i].terminate();
103 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000104 }//deactivate
105
106 /**
107 * Class that implements the listener's accept logic as a <tt>Runnable</tt>.
108 */
109 private class Acceptor implements Runnable
110 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000111 private volatile boolean m_stop = false;
112 private final ServerSocket m_serverSocket;
113
114 Acceptor() throws IOException
115 {
116 m_serverSocket = new ServerSocket(m_port, 1, InetAddress.getByName(m_ip));
117 m_serverSocket.setSoTimeout(m_soTimeout);
118 }
119
120 public void close() throws IOException
121 {
122 m_stop = true;
123 m_serverSocket.close();
124 }
125
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000126 /**
127 * Listens constantly to a server socket and handles incoming connections.
128 * One connection will be accepted and routed into the shell, all others will
129 * be notified and closed.
130 * <p/>
131 * The mechanism that should allow the thread to unblock from the ServerSocket.accept() call
132 * is currently closing the ServerSocket from another thread. When the stop flag is set,
133 * this should cause the thread to return and stop.
134 */
135 public void run()
136 {
137 try
138 {
139 /*
Richard S. Hall59aef192009-04-25 14:50:37 +0000140 A server socket is opened with a connectivity queue of a size specified
141 in int floodProtection. Concurrent login handling under normal circumstances
142 should be handled properly, but denial of service attacks via massive parallel
143 program logins should be prevented with this.
144 */
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000145 do
146 {
147 try
148 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000149 Socket s = m_serverSocket.accept();
150 if (m_useCounter.get() >= m_maxConnections)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000151 {
152 //reject with message
Richard S. Hall59aef192009-04-25 14:50:37 +0000153 PrintStream out = new PrintStream(s.getOutputStream());
154 out.print(INUSE_MESSAGE);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000155 out.flush();
156 //close
157 out.close();
158 s.close();
159 }
160 else
161 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000162 m_useCounter.increment();
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000163 //run on the connection thread
Richard S. Hall59aef192009-04-25 14:50:37 +0000164 Thread connectionThread = new Thread(new Shell(Listener.this, s, m_useCounter));
165 connectionThread.setName("telnetconsole.shell remote=" + s.getRemoteSocketAddress());
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000166 connectionThread.start();
167 }
168 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000169 catch (SocketException ex)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000170 {
171 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000172 catch (SocketTimeoutException ste)
173 {
Felix Meschberger51825702009-02-02 07:07:38 +0000174 // Caught socket timeout exception. continue
175 }
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000176 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000177 while (!m_stop);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000178
179 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000180 catch (IOException e)
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000181 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000182 m_services.error("Listener.Acceptor::run()", e);
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000183 }
184 }//run
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000185 }//inner class Acceptor
186
Richard S. Hall59aef192009-04-25 14:50:37 +0000187 private static final String INUSE_MESSAGE = "Connection refused.\r\n" + "All possible connections are currently being used.\r\n";
Felix Meschbergerefb2d082008-08-19 13:18:47 +0000188
Richard S. Hall59aef192009-04-25 14:50:37 +0000189 private int getProperty(BundleContext bundleContext, String propName, int defaultValue)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000190 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000191 String propValue = bundleContext.getProperty(propName);
192 if (propValue != null)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000193 {
194 try
195 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000196 return Integer.parseInt(propValue);
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000197 }
Richard S. Hall59aef192009-04-25 14:50:37 +0000198 catch (NumberFormatException ex)
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000199 {
Richard S. Hall55900ec2009-04-25 16:57:58 +0000200 m_services.error("Listener::activate()", ex);
Felix Meschbergerd7c324d2008-08-19 14:10:48 +0000201 }
202 }
203
204 return defaultValue;
205 }
206
Richard S. Hall59aef192009-04-25 14:50:37 +0000207 private String getProperty(BundleContext bundleContext, String propName, String defaultValue)
Felix Meschberger35901032009-01-30 22:43:37 +0000208 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000209 String propValue = bundleContext.getProperty(propName);
210 if (propValue != null)
Felix Meschberger35901032009-01-30 22:43:37 +0000211 {
Felix Meschberger73bf1232009-02-02 08:28:42 +0000212 return propValue;
Felix Meschberger35901032009-01-30 22:43:37 +0000213 }
214
215 return defaultValue;
216 }
217
Felix Meschberger73bf1232009-02-02 08:28:42 +0000218 /**
219 * Registers the given {@link Shell} instance handling a remote connection
220 * to this listener.
221 * <p>
222 * This method is called by the {@link Shell#run()} method to register the
223 * remote connection for it to be terminated in case this listener is
224 * {@link #deactivate() deactivated} before the remote connection is
225 * terminated.
226 *
227 * @param connection The {@link Shell} connection to register
228 */
Richard S. Hall59aef192009-04-25 14:50:37 +0000229 void registerConnection(Shell connection)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000230 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000231 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000232 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000233 m_connections.add(connection);
Felix Meschberger73bf1232009-02-02 08:28:42 +0000234 }
235 }
236
237 /**
238 * Unregisters the given {@link Shell} instance handling a remote connection
239 * from this listener.
240 * <p>
241 * This method is called when the {@link Shell#run()} method terminates to
242 * inform this listener instance that the remote connection has ended and
243 * thus does not need to be cleaned up when this listener terminates.
244 *
245 * @param connection The {@link Shell} connection to unregister
246 */
Richard S. Hall59aef192009-04-25 14:50:37 +0000247 void unregisterConnection(Shell connection)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000248 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000249 synchronized (m_connections)
Felix Meschberger73bf1232009-02-02 08:28:42 +0000250 {
Richard S. Hall59aef192009-04-25 14:50:37 +0000251 m_connections.remove(connection);
Felix Meschberger73bf1232009-02-02 08:28:42 +0000252 }
253 }
Richard S. Hall55900ec2009-04-25 16:57:58 +0000254}//class Listener