| 1 | // $Id: LoginManager.java,v 1.46 2007-10-22 12:30:38 behrmann Exp $ |
| 2 | // |
| 3 | package dmg.cells.services.login ; |
| 4 | |
| 5 | import java.lang.reflect.* ; |
| 6 | import java.net.* ; |
| 7 | import java.io.* ; |
| 8 | import java.nio.channels.*; |
| 9 | import java.util.*; |
| 10 | |
| 11 | import org.slf4j.Logger; |
| 12 | import org.slf4j.LoggerFactory; |
| 13 | |
| 14 | import dmg.cells.nucleus.*; |
| 15 | import dmg.util.*; |
| 16 | import dmg.protocols.ssh.* ; |
| 17 | import dmg.protocols.telnet.* ; |
| 18 | |
| 19 | /** |
| 20 | ** |
| 21 | * |
| 22 | * |
| 23 | * @author Patrick Fuhrmann |
| 24 | * @version 0.1, 15 Feb 1998 |
| 25 | * |
| 26 | */ |
| 27 | public class LoginManager |
| 28 | extends CellAdapter |
| 29 | implements UserValidatable { |
| 30 | |
| 31 | private final String _cellName ; |
| 32 | private final CellNucleus _nucleus ; |
| 33 | private final Args _args ; |
| 34 | private final ListenThread _listenThread ; |
| 35 | private int _connectionRequestCounter = 0 ; |
| 36 | private int _connectionAcceptionCounter = 0 ; |
| 37 | private int _connectionDeniedCounter = 0 ; |
| 38 | private String _locationManager = null ; |
| 39 | private int _loginCounter = 0 , _loginFailures = 0 ; |
| 40 | private boolean _sending = false ; |
| 41 | private Class _loginClass = Object.class ; |
| 42 | private Constructor _loginConstructor = null ; |
| 43 | private Constructor _authConstructor = null ; |
| 44 | private Method _loginPrintMethod = null ; |
| 45 | private int _maxLogin = -1 ; |
| 46 | private final Map<String,Object> _childHash = new HashMap<String,Object>() ; |
| 47 | |
| 48 | /** |
| 49 | * actually, _childCount have to be equal to _childHash.size(). But while |
| 50 | * cells needs some time to die, _childHash contains cells which are in removing state, |
| 51 | * while _childCount shows active cells only. |
| 52 | */ |
| 53 | private int _childCount = 0 ; |
| 54 | private String _authenticator = null ; |
| 55 | private KeepAliveThread _keepAlive = null ; |
| 56 | |
| 57 | private LoginBrokerHandler _loginBrokerHandler = null ; |
| 58 | |
| 59 | private static Logger _log = LoggerFactory.getLogger(LoginManager.class); |
| 60 | private static Logger _logSocketIO = LoggerFactory.getLogger("logger.dev.org.dcache.io.socket"); |
| 61 | |
| 62 | private Class [][] _loginConSignature = { |
| 63 | { java.lang.String.class , |
| 64 | dmg.util.StreamEngine.class } , |
| 65 | { java.lang.String.class , |
| 66 | dmg.util.StreamEngine.class , |
| 67 | dmg.util.Args.class } |
| 68 | } ; |
| 69 | |
| 70 | private Class [] _authConSignature = { |
| 71 | dmg.cells.nucleus.CellNucleus.class , dmg.util.Args.class |
| 72 | } ; |
| 73 | |
| 74 | |
| 75 | private Class [] _loginPntSignature = { int.class } ; |
| 76 | private int _loginConType = -1 ; |
| 77 | |
| 78 | private String _protocol ; |
| 79 | private String _authClassName ; |
| 80 | private Class _authClass ; |
| 81 | /** |
| 82 | *<pre> |
| 83 | * usage <listenPort> <loginCellClass> |
| 84 | * [-prot=ssh|telnet|raw] |
| 85 | * default : telnet |
| 86 | * [-auth=<authenticationClass>] |
| 87 | * default : ssh : dmg.cells.services.login.SshSAuth_A |
| 88 | * telnet : dmg.cells.services.login.TelnetSAuth_A |
| 89 | * raw : none |
| 90 | * |
| 91 | * all residual arguments and all options are sent to |
| 92 | * the <loginCellClass> : |
| 93 | * <init>(String name , StreamEngine engine , Args args ) |
| 94 | * |
| 95 | * and to the Authentication module (class) |
| 96 | * |
| 97 | * <init>(CellNucleus nucleus , Args args ) |
| 98 | * |
| 99 | * Both get their own copy. |
| 100 | *</pre> |
| 101 | */ |
| 102 | public LoginManager( String name , String argString ) throws Exception { |
| 103 | |
| 104 | super( name , argString , false ) ; |
| 105 | |
| 106 | _cellName = name ; |
| 107 | _nucleus = getNucleus() ; |
| 108 | _args = getArgs() ; |
| 109 | try{ |
| 110 | Args args = _args ; |
| 111 | if( args.argc() < 2 ) |
| 112 | throw new |
| 113 | IllegalArgumentException( |
| 114 | "USAGE : ... <listenPort> <loginCellClass>"+ |
| 115 | " [-prot=ssh|telnet|raw] [-auth=<authCell>]"+ |
| 116 | " [-maxLogin=<n>|-1]"+ |
| 117 | " [-keepAlive=<seconds>]"+ |
| 118 | " [-acceptErrorWait=<msecs>]"+ |
| 119 | " [args givenToLoginClass]" ) ; |
| 120 | // |
| 121 | // get the protocol |
| 122 | // |
| 123 | _protocol = args.getOpt("prot") ; |
| 124 | if( _protocol == null )_protocol = "telnet" ; |
| 125 | |
| 126 | if( ! ( _protocol.equals("ssh") || |
| 127 | _protocol.equals("telnet" ) || |
| 128 | _protocol.equals("raw" ) ) ) |
| 129 | throw new |
| 130 | IllegalArgumentException( |
| 131 | "Protocol must be telnet or ssh or raw" ) ; |
| 132 | |
| 133 | _log.info( "Using Protocol : "+_protocol ) ; |
| 134 | // |
| 135 | // get the listen port. |
| 136 | // |
| 137 | int listenPort = Integer.parseInt( args.argv(0) ) ; |
| 138 | args.shift() ; |
| 139 | // |
| 140 | // which cell to start |
| 141 | // |
| 142 | if( args.argc() > 0 ){ |
| 143 | _loginClass = Class.forName( args.argv(0) ) ; |
| 144 | _log.info( "Using login class : "+_loginClass.getName() ) ; |
| 145 | args.shift() ; |
| 146 | } |
| 147 | // |
| 148 | // get the authentication |
| 149 | // |
| 150 | _authenticator = args.getOpt("authenticator") ; |
| 151 | _authenticator = _authenticator == null ? "pam" : _authenticator ; |
| 152 | |
| 153 | if( ( _authClassName = args.getOpt("auth") ) == null ){ |
| 154 | if( _protocol.equals( "ssh" ) ){ |
| 155 | _authClass = dmg.cells.services.login.SshSAuth_A.class ; |
| 156 | }else if( _protocol.equals( "raw" ) ){ |
| 157 | _authClass = null ; |
| 158 | }else if( _protocol.equals( "telnet" ) ){ |
| 159 | _authClass = dmg.cells.services.login.TelnetSAuth_A.class ; |
| 160 | } |
| 161 | if( _authClass != null ) |
| 162 | _log.info( "Using authentication Module : "+_authClass ) ; |
| 163 | }else if( _authClassName.equals( "none" ) ){ |
| 164 | // _authClass = dmg.cells.services.login.NoneSAuth.class ; |
| 165 | }else{ |
| 166 | _log.info( "Using authentication Module : "+_authClassName ) ; |
| 167 | _authClass = Class.forName(_authClassName) ; |
| 168 | } |
| 169 | if( _authClass != null ){ |
| 170 | _authConstructor = _authClass.getConstructor( _authConSignature ) ; |
| 171 | _log.info( "Using authentication Constructor : "+_authConstructor ) ; |
| 172 | }else{ |
| 173 | _authConstructor = null ; |
| 174 | _log.info( "No authentication used" ) ; |
| 175 | } |
| 176 | try{ |
| 177 | _loginConstructor = _loginClass.getConstructor( _loginConSignature[1] ) ; |
| 178 | _loginConType = 1 ; |
| 179 | }catch( NoSuchMethodException nsme ){ |
| 180 | _loginConstructor = _loginClass.getConstructor( _loginConSignature[0] ) ; |
| 181 | _loginConType = 0 ; |
| 182 | } |
| 183 | _log.info( "Using constructor : "+_loginConstructor ) ; |
| 184 | try{ |
| 185 | |
| 186 | _loginPrintMethod = _loginClass.getMethod( |
| 187 | "setPrintoutLevel" , |
| 188 | _loginPntSignature ) ; |
| 189 | |
| 190 | }catch( NoSuchMethodException pr ){ |
| 191 | _log.info( "No setPrintoutLevel(int) found in "+_loginClass.getName() ) ; |
| 192 | _loginPrintMethod = null ; |
| 193 | } |
| 194 | String maxLogin = args.getOpt("maxLogin") ; |
| 195 | if( maxLogin != null ){ |
| 196 | try{ |
| 197 | _maxLogin = Integer.parseInt(maxLogin); |
| 198 | }catch(NumberFormatException ee){/* bad values ignored */} |
| 199 | } |
| 200 | // |
| 201 | // using the LoginBroker ? |
| 202 | // |
| 203 | _loginBrokerHandler = new LoginBrokerHandler() ; |
| 204 | addCommandListener( _loginBrokerHandler ) ; |
| 205 | // |
| 206 | // enforce 'maxLogin' if 'loginBroker' is defined |
| 207 | // |
| 208 | if( ( _loginBrokerHandler.isActive() ) && |
| 209 | ( _maxLogin < 0 ) ) _maxLogin=100000 ; |
| 210 | // |
| 211 | if( _maxLogin < 0 ){ |
| 212 | _log.info("MaxLogin feature disabled") ; |
| 213 | }else{ |
| 214 | |
| 215 | _nucleus.addCellEventListener( new LoginEventListener() ) ; |
| 216 | |
| 217 | _log.info("Maximum Logins set to :"+_maxLogin ) ; |
| 218 | } |
| 219 | // |
| 220 | // keep alive |
| 221 | // |
| 222 | String keepAliveValue = args.getOpt("keepAlive"); |
| 223 | long keepAlive = 0L ; |
| 224 | try{ |
| 225 | keepAlive = keepAliveValue == null ? 0L : |
| 226 | Long.parseLong(keepAliveValue); |
| 227 | }catch(NumberFormatException ee ){ |
| 228 | _log.warn("KeepAlive value not valid : "+keepAliveValue ) ; |
| 229 | } |
| 230 | _log.info("Keep Alive set to "+keepAlive+" seconds") ; |
| 231 | keepAlive *= 1000L ; |
| 232 | _keepAlive = new KeepAliveThread(keepAlive) ; |
| 233 | // |
| 234 | // get the location manager |
| 235 | // |
| 236 | _locationManager = args.getOpt("lm") ; |
| 237 | // |
| 238 | |
| 239 | _listenThread = new ListenThread( listenPort ) ; |
| 240 | _log.info( "Listening on port "+_listenThread.getListenPort() ) ; |
| 241 | |
| 242 | |
| 243 | _nucleus.newThread( _listenThread , "listen" ).start() ; |
| 244 | |
| 245 | _nucleus.newThread( new LocationThread() , "Location" ).start() ; |
| 246 | |
| 247 | _nucleus.newThread( _keepAlive , "KeepAlive" ).start() ; |
| 248 | |
| 249 | }catch( Exception e ){ |
| 250 | _log.warn( "LoginManger >"+getCellName()+"< got exception : "+e, e ) ; |
| 251 | start() ; |
| 252 | kill() ; |
| 253 | throw e ; |
| 254 | } |
| 255 | |
| 256 | start() ; |
| 257 | |
| 258 | } |
| 259 | @Override |
| 260 | public CellVersion getCellVersion(){ |
| 261 | try{ |
| 262 | |
| 263 | Method m = _loginClass.getMethod( "getStaticCellVersion" , (Class[])null ) ; |
| 264 | |
| 265 | return (CellVersion)m.invoke( (Object)null , (Object[])null ) ; |
| 266 | |
| 267 | }catch(Exception ee ){ |
| 268 | return super.getCellVersion() ; |
| 269 | } |
| 270 | } |
| 271 | public class LoginBrokerHandler implements Runnable { |
| 272 | |
| 273 | private String _loginBroker = null ; |
| 274 | private String _protocolFamily = null ; |
| 275 | private String _protocolVersion = null ; |
| 276 | private long _brokerUpdateTime = 5*60*1000 ; |
| 277 | private double _brokerUpdateOffset = 0.1 ; |
| 278 | private LoginBrokerInfo _info = null ; |
| 279 | private double _currentLoad = 0.0 ; |
| 280 | private LoginBrokerHandler(){ |
| 281 | |
| 282 | _loginBroker = _args.getOpt( "loginBroker" ) ; |
| 283 | if( _loginBroker == null )return; |
| 284 | |
| 285 | _protocolFamily = _args.getOpt("protocolFamily" ) ; |
| 286 | if( _protocolFamily == null )_protocolFamily = _protocol ; |
| 287 | _protocolVersion = _args.getOpt("protocolVersion") ; |
| 288 | if( _protocolVersion == null )_protocolVersion = "0.1" ; |
| 289 | String tmp = _args.getOpt("brokerUpdateTime") ; |
| 290 | try{ |
| 291 | _brokerUpdateTime = Long.parseLong(tmp) * 1000 ; |
| 292 | }catch(NumberFormatException e ){/* bad values ignored */ } |
| 293 | tmp = _args.getOpt("brokerUpdateOffset") ; |
| 294 | if(tmp != null) { |
| 295 | try{ |
| 296 | _brokerUpdateOffset = Double.parseDouble(tmp) ; |
| 297 | }catch(NumberFormatException e ){/* bad values ignored */ } |
| 298 | } |
| 299 | |
| 300 | _info = new LoginBrokerInfo( |
| 301 | _nucleus.getCellName() , |
| 302 | _nucleus.getCellDomainName() , |
| 303 | _protocolFamily , |
| 304 | _protocolVersion , |
| 305 | _loginClass.getName() ) ; |
| 306 | |
| 307 | _info.setUpdateTime( _brokerUpdateTime ) ; |
| 308 | |
| 309 | _nucleus.newThread( this , "loginBrokerHandler" ).start() ; |
| 310 | |
| 311 | } |
| 312 | public void run(){ |
| 313 | try{ |
| 314 | synchronized(this){ |
| 315 | while( ! Thread.interrupted() ){ |
| 316 | try{ |
| 317 | runUpdate() ; |
| 318 | }catch(Exception ie){ |
| 319 | _log.warn("Login Broker Thread reports : "+ie); |
| 320 | } |
| 321 | wait( _brokerUpdateTime ) ; |
| 322 | } |
| 323 | } |
| 324 | }catch( Exception io ){ |
| 325 | _log.info( "Login Broker Thread terminated due to "+io ) ; |
| 326 | } |
| 327 | } |
| 328 | public String hh_get_children = "[-binary]" ; |
| 329 | public Object ac_get_children( Args args ){ |
| 330 | boolean binary = args.getOpt("binary") != null ; |
| 331 | synchronized( _childHash ){ |
| 332 | if( binary ){ |
| 333 | String [] list = new String[_childHash.size()] ; |
| 334 | list = _childHash.keySet().toArray(list); |
| 335 | return new LoginManagerChildrenInfo( getCellName() , getCellDomainName(), list ) ; |
| 336 | }else{ |
| 337 | StringBuilder sb = new StringBuilder() ; |
| 338 | for(String child : _childHash.keySet() ){ |
| 339 | sb.append(child).append("\n"); |
| 340 | } |
| 341 | return sb.toString(); |
| 342 | } |
| 343 | } |
| 344 | } |
| 345 | public String hh_lb_set_update = "<updateTime/sec>" ; |
| 346 | public String ac_lb_set_update_$_1( Args args ){ |
| 347 | long update = Long.parseLong( args.argv(0) )*1000 ; |
| 348 | if( update < 2000 ) |
| 349 | throw new |
| 350 | IllegalArgumentException("Update time out of range") ; |
| 351 | |
| 352 | synchronized(this){ |
| 353 | _brokerUpdateTime = update ; |
| 354 | _info.setUpdateTime(update) ; |
| 355 | notifyAll() ; |
| 356 | } |
| 357 | return "" ; |
| 358 | } |
| 359 | private synchronized void runUpdate(){ |
| 360 | |
| 361 | if( _listenThread == null ) return; |
| 362 | |
| 363 | InetAddress[] addresses = _listenThread.getInetAddress(); |
| 364 | |
| 365 | if( (addresses == null) || ( addresses.length == 0 ) ) return; |
| 366 | |
| 367 | String[] hosts = new String[addresses.length]; |
| 368 | |
| 369 | /** |
| 370 | * Add addresses ensuring preferred ordering: external addresses are before any |
| 371 | * internal interface addresses. |
| 372 | */ |
| 373 | int nextExternalIfIndex = 0; |
| 374 | int nextInternalIfIndex = addresses.length-1; |
| 375 | |
| 376 | for( int i = 0; i < addresses.length; i++) { |
| 377 | InetAddress addr = addresses[i]; |
| 378 | |
| 379 | if( !addr.isLinkLocalAddress() && !addr.isLoopbackAddress() && |
| 380 | !addr.isSiteLocalAddress() && !addr.isMulticastAddress()) { |
| 381 | hosts [nextExternalIfIndex++] = addr.getHostName(); |
| 382 | } else { |
| 383 | hosts [nextInternalIfIndex--] = addr.getHostName(); |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | _info.setHosts(hosts); |
| 388 | _info.setPort(_listenThread.getListenPort()); |
| 389 | _info.setLoad(_currentLoad); |
| 390 | try{ |
| 391 | sendMessage(new CellMessage(new CellPath(_loginBroker),_info)); |
| 392 | // _log.info("Updated : "+_info); |
| 393 | }catch(Exception ee){} |
| 394 | } |
| 395 | public void getInfo( PrintWriter pw ){ |
| 396 | if( _loginBroker == null ){ |
| 397 | pw.println( " Login Broker : DISABLED" ) ; |
| 398 | return ; |
| 399 | } |
| 400 | pw.println( " LoginBroker : "+_loginBroker ) ; |
| 401 | pw.println( " Protocol Family : "+_protocolFamily ) ; |
| 402 | pw.println( " Protocol Version : "+_protocolVersion ) ; |
| 403 | pw.println( " Update Time : "+(_brokerUpdateTime/1000)+" seconds" ) ; |
| 404 | pw.println( " Update Offset : "+ |
| 405 | ((int)(_brokerUpdateOffset*100.))+" %" ) ; |
| 406 | |
| 407 | } |
| 408 | private boolean isActive(){ return _loginBroker != null ; } |
| 409 | private void loadChanged( int children , int maxChildren ){ |
| 410 | if( _loginBroker == null )return ; |
| 411 | synchronized( this ){ |
| 412 | _currentLoad = (double)children / (double) maxChildren ; |
| 413 | if( Math.abs( _info.getLoad() - _currentLoad ) > _brokerUpdateOffset ){ |
| 414 | notifyAll() ; |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | } |
| 419 | private class LoginEventListener implements CellEventListener { |
| 420 | public void cellCreated( CellEvent ce ) { /* forced by interface */ } |
| 421 | public void cellDied( CellEvent ce ) { |
| 422 | synchronized( _childHash ){ |
| 423 | String removedCell = ce.getSource().toString() ; |
| 424 | if( ! removedCell.startsWith( getCellName() ) )return ; |
| 425 | |
| 426 | /* |
| 427 | * while in some cases remove may be issued prior cell is inserted into _childHash |
| 428 | * following trick is used: |
| 429 | * if there is no mapping for this cell, we create a 'dead' mapping, which will |
| 430 | * allow following put to identify it as a 'dead' and remove it. |
| 431 | * |
| 432 | */ |
| 433 | |
| 434 | Object newCell = _childHash.remove( removedCell ) ; |
| 435 | if( newCell == null ) { |
| 436 | // it's a dead cell, put it back |
| 437 | _childHash.put(removedCell, new Object() ); |
| 438 | _log.warn("LoginEventListener : removing DEAD cell: "+removedCell); |
| 439 | } |
| 440 | _log.info("LoginEventListener : removing : "+removedCell); |
| 441 | _childCount -- ; |
| 442 | childrenCounterChanged() ; |
| 443 | } |
| 444 | } |
| 445 | public void cellExported( CellEvent ce ) { /* forced by interface */ } |
| 446 | public void routeAdded( CellEvent ce ) { /* forced by interface */ } |
| 447 | public void routeDeleted( CellEvent ce ) { /* forced by interface */ } |
| 448 | } |
| 449 | // |
| 450 | // the 'send to location manager thread' |
| 451 | // |
| 452 | private class LocationThread implements Runnable { |
| 453 | public void run(){ |
| 454 | |
| 455 | int listenPort = _listenThread.getListenPort() ; |
| 456 | |
| 457 | _log.info("Sending 'listeningOn "+getCellName()+" "+listenPort+"'") ; |
| 458 | _sending = true ; |
| 459 | String dest = _locationManager; |
| 460 | if( dest == null )return ; |
| 461 | CellPath path = new CellPath( dest ) ; |
| 462 | CellMessage msg = |
| 463 | new CellMessage( |
| 464 | path , |
| 465 | "listening on "+getCellName()+" "+listenPort ) ; |
| 466 | |
| 467 | for( int i = 0 ; ! Thread.interrupted() ; i++ ){ |
| 468 | _log.info("Sending ("+i+") 'listening on "+getCellName()+" "+listenPort+"'") ; |
| 469 | |
| 470 | try{ |
| 471 | if( sendAndWait( msg , 5000 ) != null ){ |
| 472 | _log.info("Portnumber successfully sent to "+dest ) ; |
| 473 | _sending = false ; |
| 474 | break ; |
| 475 | } |
| 476 | _log.warn( "No reply from "+dest ) ; |
| 477 | }catch( InterruptedException ie ){ |
| 478 | _log.warn( "'send portnumber thread' interrupted"); |
| 479 | break ; |
| 480 | }catch(Exception ee ){ |
| 481 | _log.warn( "Problem sending portnumber "+ee ) ; |
| 482 | } |
| 483 | try{ |
| 484 | Thread.sleep(10000) ; |
| 485 | }catch(InterruptedException ie ){ |
| 486 | _log.warn( "'send portnumber thread' (sleep) interrupted"); |
| 487 | break ; |
| 488 | |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | } |
| 493 | private class KeepAliveThread implements Runnable { |
| 494 | private long _keepAlive = 0L ; |
| 495 | private final Object _lock = new Object() ; |
| 496 | private KeepAliveThread( long keepAlive ){ |
| 497 | _keepAlive = keepAlive ; |
| 498 | } |
| 499 | public void run(){ |
| 500 | synchronized( _lock ){ |
| 501 | _log.info("KeepAlive Thread started"); |
| 502 | while( ! Thread.interrupted() ){ |
| 503 | try{ |
| 504 | if( _keepAlive < 1 ){ |
| 505 | _lock.wait() ; |
| 506 | }else{ |
| 507 | _lock.wait( _keepAlive ) ; |
| 508 | } |
| 509 | }catch(InterruptedException ie ){ |
| 510 | _log.info("KeepAlive thread done (interrupted)"); |
| 511 | break ; |
| 512 | } |
| 513 | |
| 514 | if( _keepAlive > 0 ) |
| 515 | try{ |
| 516 | runKeepAlive(); |
| 517 | }catch(Throwable t ){ |
| 518 | _log.warn("runKeepAlive reported : "+t); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | } |
| 523 | |
| 524 | } |
| 525 | private void setKeepAlive( long keepAlive ){ |
| 526 | synchronized( _lock ){ |
| 527 | _keepAlive = keepAlive ; |
| 528 | _log.info("Keep Alive value changed to "+_keepAlive); |
| 529 | _lock.notifyAll() ; |
| 530 | } |
| 531 | } |
| 532 | private long getKeepAlive(){ |
| 533 | return _keepAlive ; |
| 534 | } |
| 535 | } |
| 536 | public String hh_set_keepalive = "<keepAliveValue/seconds>"; |
| 537 | public String ac_set_keepalive_$_1( Args args ){ |
| 538 | long keepAlive = Long.parseLong( args.argv(0) ) ; |
| 539 | _keepAlive.setKeepAlive( keepAlive * 1000L ) ; |
| 540 | return "keepAlive value set to "+keepAlive+" seconds" ; |
| 541 | } |
| 542 | |
| 543 | public void runKeepAlive(){ |
| 544 | List<Object> list = null ; |
| 545 | synchronized( _childHash ){ |
| 546 | list = new ArrayList<Object>( _childHash.values() ) ; |
| 547 | } |
| 548 | |
| 549 | for( Object o : list ){ |
| 550 | |
| 551 | if( ! ( o instanceof KeepAliveListener ) )continue ; |
| 552 | try{ |
| 553 | ((KeepAliveListener)o).keepAlive() ; |
| 554 | }catch(Throwable t ){ |
| 555 | _log.warn("Problem reported by : "+o+" : "+t); |
| 556 | } |
| 557 | } |
| 558 | } |
| 559 | // |
| 560 | // the cell implementation |
| 561 | // |
| 562 | @Override |
| 563 | public String toString(){ |
| 564 | return |
| 565 | "p="+(_listenThread==null?"???":(""+_listenThread.getListenPort()))+ |
| 566 | ";c="+_loginClass.getName() ; |
| 567 | } |
| 568 | @Override |
| 569 | public void getInfo( PrintWriter pw ){ |
| 570 | pw.println( " -- Login Manager $Revision: 1.46 $") ; |
| 571 | pw.println( " Listen Port : "+_listenThread.getListenPort() ) ; |
| 572 | pw.println( " Login Class : "+_loginClass ) ; |
| 573 | pw.println( " Protocol : "+_protocol ) ; |
| 574 | pw.println( " NioChannel : "+( _listenThread._serverSocket.getChannel() != null ) ) ; |
| 575 | pw.println( " Auth Class : "+_authClass ) ; |
| 576 | pw.println( " Logins created : "+_loginCounter ) ; |
| 577 | pw.println( " Logins failed : "+_loginFailures ) ; |
| 578 | pw.println( " Logins denied : "+_connectionDeniedCounter ) ; |
| 579 | pw.println( " KeepAlive : "+(_keepAlive.getKeepAlive()/1000L) ) ; |
| 580 | |
| 581 | if( _maxLogin > -1 ) |
| 582 | pw.println( " Logins/max : "+_childHash.size()+"("+_childCount+")/"+_maxLogin ) ; |
| 583 | |
| 584 | if( _locationManager != null ) |
| 585 | pw.println( " Location Mgr : "+_locationManager+ |
| 586 | " ("+(_sending?"Sending":"Informed")+")" ) ; |
| 587 | |
| 588 | if( _loginBrokerHandler != null ){ |
| 589 | pw.println( " LoginBroker Info :" ) ; |
| 590 | _loginBrokerHandler.getInfo( pw ) ; |
| 591 | } |
| 592 | return ; |
| 593 | } |
| 594 | public String hh_set_max_logins = "<maxNumberOfLogins>|-1" ; |
| 595 | public String ac_set_max_logins_$_1( Args args )throws Exception { |
| 596 | int n = Integer.parseInt( args.argv(0) ) ; |
| 597 | if( ( n > -1 ) && ( _maxLogin < 0 ) ) |
| 598 | throw new |
| 599 | IllegalArgumentException("Can't switch off maxLogin feature" ) ; |
| 600 | if( ( n < 0 ) && ( _maxLogin > -1 ) ) |
| 601 | throw new |
| 602 | IllegalArgumentException( "Can't switch on maxLogin feature" ) ; |
| 603 | |
| 604 | synchronized( _childHash ){ |
| 605 | _maxLogin = n ; |
| 606 | childrenCounterChanged() ; |
| 607 | } |
| 608 | return "" ; |
| 609 | } |
| 610 | @Override |
| 611 | public void cleanUp(){ |
| 612 | _log.info( "cleanUp requested by nucleus, closing listen socket" ) ; |
| 613 | if( _listenThread != null )_listenThread.shutdown() ; |
| 614 | _log.info( "Bye Bye" ) ; |
| 615 | } |
| 616 | |
| 617 | private class ListenThread implements Runnable { |
| 618 | private int _listenPort = 0 ; |
| 619 | private ServerSocket _serverSocket = null ; |
| 620 | private boolean _shutdown = false ; |
| 621 | private boolean _active = true ; |
| 622 | private Thread _this = null ; |
| 623 | private long _acceptErrorTimeout = 0L ; |
| 624 | private boolean _isDedicated = false; |
| 625 | |
| 626 | private ListenThread( int listenPort) throws Exception { |
| 627 | _listenPort = listenPort ; |
| 628 | |
| 629 | try{ |
| 630 | _acceptErrorTimeout = Long.parseLong(_args.getOpt("acceptErrorWait")); |
| 631 | }catch(NumberFormatException ee ){ /* bad values ignored */}; |
| 632 | |
| 633 | openPort() ; |
| 634 | } |
| 635 | private void openPort() throws Exception { |
| 636 | |
| 637 | String ssf = _args.getOpt("socketfactory") ; |
| 638 | String local = _args.getOpt("listen"); |
| 639 | |
| 640 | if( ssf == null ){ |
| 641 | SocketAddress socketAddress = null; |
| 642 | |
| 643 | if ( (local == null ) || local.equals("*") || local.equals("") ) { |
| 644 | socketAddress = new InetSocketAddress( _listenPort ) ; |
| 645 | }else{ |
| 646 | socketAddress = new InetSocketAddress( InetAddress.getByName(local) , _listenPort ) ; |
| 647 | _isDedicated = true; |
| 648 | } |
| 649 | |
| 650 | _serverSocket = ServerSocketChannel.open().socket(); |
| 651 | _serverSocket.bind( socketAddress ); |
| 652 | _listenPort = _serverSocket.getLocalPort() ; |
| 653 | |
| 654 | }else{ |
| 655 | StringTokenizer st = new StringTokenizer(ssf,","); |
| 656 | |
| 657 | /* |
| 658 | * socket factory initialization has following format: |
| 659 | * <classname>[<arg1>,...] |
| 660 | */ |
| 661 | if( st.countTokens() < 2 ) { |
| 662 | throw new |
| 663 | IllegalArgumentException( "Invalid Arguments for 'socketfactory'"); |
| 664 | } |
| 665 | |
| 666 | String tunnelFactoryClass = st.nextToken(); |
| 667 | /* |
| 668 | * the rest is passed to factory constructor as String[] |
| 669 | */ |
| 670 | String[] farctoryArgs = new String[ st.countTokens()]; |
| 671 | for( int i = 0; st.hasMoreTokens() ; i++) { |
| 672 | farctoryArgs[i] = st.nextToken(); |
| 673 | } |
| 674 | |
| 675 | |
| 676 | Class [] constructorArgClassA = { java.lang.String[].class , java.util.Map.class } ; |
| 677 | Class [] constructorArgClassB = { java.lang.String[].class } ; |
| 678 | |
| 679 | |
| 680 | Class ssfClass = Class.forName(tunnelFactoryClass); |
| 681 | Object [] args = null ; |
| 682 | |
| 683 | Constructor ssfConstructor = null ; |
| 684 | try{ |
| 685 | ssfConstructor = ssfClass.getConstructor(constructorArgClassA) ; |
| 686 | args = new Object[2] ; |
| 687 | args[0] = farctoryArgs; |
| 688 | Map map = new HashMap((Map)getDomainContext()) ; |
| 689 | map.put( "UserValidatable" , LoginManager.this ) ; |
| 690 | args[1] = map ; |
| 691 | }catch( Exception ee ){ |
| 692 | ssfConstructor = ssfClass.getConstructor(constructorArgClassB) ; |
| 693 | args = new Object[1] ; |
| 694 | args[0] = farctoryArgs; |
| 695 | } |
| 696 | Object obj = ssfConstructor.newInstance(args) ; |
| 697 | |
| 698 | Method meth = ssfClass.getMethod("createServerSocket", new Class[0]) ; |
| 699 | _serverSocket = (ServerSocket)meth.invoke( obj ) ; |
| 700 | |
| 701 | if ( (local == null ) || local.equals("*") || local.equals("") ) { |
| 702 | _serverSocket.bind(new InetSocketAddress( _listenPort ) ); |
| 703 | }else{ |
| 704 | _serverSocket.bind(new InetSocketAddress(InetAddress.getByName(local), _listenPort ) ); |
| 705 | _isDedicated = true; |
| 706 | } |
| 707 | |
| 708 | _log.info("ListenThread : got serverSocket class : "+_serverSocket.getClass().getName()); |
| 709 | } |
| 710 | |
| 711 | if( _logSocketIO.isDebugEnabled() ) { |
| 712 | _logSocketIO.debug("Socket BIND local = " + _serverSocket.getInetAddress() + ":" + _serverSocket.getLocalPort() ); |
| 713 | } |
| 714 | _log.info("Nio Socket Channel : "+(_serverSocket.getChannel()!=null)); |
| 715 | } |
| 716 | public int getListenPort(){ return _listenPort ; } |
| 717 | public InetAddress[] getInetAddress(){ |
| 718 | InetAddress[] addresses = null; |
| 719 | if( _isDedicated ) { |
| 720 | if( _serverSocket != null ) { |
| 721 | addresses = new InetAddress[1]; |
| 722 | addresses[0] = _serverSocket.getInetAddress() ; |
| 723 | } |
| 724 | }else{ |
| 725 | |
| 726 | /** |
| 727 | * put all local Ip addresses, except loopback |
| 728 | */ |
| 729 | |
| 730 | try { |
| 731 | Enumeration<NetworkInterface> ifList = NetworkInterface.getNetworkInterfaces(); |
| 732 | |
| 733 | Vector<InetAddress> v = new Vector<InetAddress>(); |
| 734 | while( ifList.hasMoreElements() ) { |
| 735 | |
| 736 | NetworkInterface ne = ifList.nextElement(); |
| 737 | |
| 738 | Enumeration<InetAddress> ipList = ne.getInetAddresses(); |
| 739 | while( ipList.hasMoreElements() ) { |
| 740 | InetAddress ia = ipList.nextElement(); |
| 741 | // Currently we do not handle ipv6 |
| 742 | if( ! (ia instanceof Inet4Address) ) continue; |
| 743 | if( ! ia.isLoopbackAddress() ) { |
| 744 | v.add( ia ) ; |
| 745 | } |
| 746 | } |
| 747 | } |
| 748 | addresses = v.toArray( new InetAddress[ v.size() ] ); |
| 749 | }catch(SocketException se_ignored) {} |
| 750 | } |
| 751 | |
| 752 | return addresses; |
| 753 | } |
| 754 | |
| 755 | public void run(){ |
| 756 | _this = Thread.currentThread() ; |
| 757 | while( true ){ |
| 758 | Socket socket = null ; |
| 759 | try{ |
| 760 | socket = _serverSocket.accept() ; |
| 761 | socket.setKeepAlive(true); |
| 762 | if( _logSocketIO.isDebugEnabled() ) { |
| 763 | _logSocketIO.debug("Socket OPEN (ACCEPT) remote = " + socket.getInetAddress() + ":" + socket.getPort() + |
| 764 | " local = " +socket.getLocalAddress() + ":" + socket.getLocalPort() ); |
| 765 | } |
| 766 | _log.info("Nio Channel (accept) : "+(socket.getChannel()!=null)); |
| 767 | |
| 768 | |
| 769 | _connectionRequestCounter ++ ; |
| 770 | int currentChildHash = 0 ; |
| 771 | synchronized( _childHash ){ currentChildHash = _childCount ; } |
| 772 | _log.info("New connection : "+currentChildHash); |
| 773 | if ((_maxLogin > 0) && (currentChildHash > _maxLogin)) { |
| 774 | _connectionDeniedCounter++; |
| 775 | _log.warn("Connection denied " + currentChildHash + " > " |
| 776 | + _maxLogin); |
| 777 | _logSocketIO.warn("number of allowed logins exceeded."); |
| 778 | new ShutdownEngine(socket); |
| 779 | continue; |
| 780 | } |
| 781 | _log.info( "Connection request from "+socket.getInetAddress() ) ; |
| 782 | synchronized( _childHash ){ _childCount ++; } |
| 783 | _nucleus.newThread( |
| 784 | new RunEngineThread(socket) , |
| 785 | "ClinetThread-" + socket.getInetAddress() + ":" + socket.getPort() ).start() ; |
| 786 | |
| 787 | }catch( InterruptedIOException ioe ){ |
| 788 | _log.warn("Listen thread interrupted") ; |
| 789 | try{ _serverSocket.close() ; }catch(IOException ee){} |
| 790 | break ; |
| 791 | }catch( IOException ioe ){ |
| 792 | if (_serverSocket.isClosed()) { |
| 793 | break; |
| 794 | } |
| 795 | |
| 796 | _log.warn( "Got an IO Exception ( closing server ) : "+ioe ) ; |
| 797 | try{ _serverSocket.close() ; }catch(IOException ee){} |
| 798 | if( _acceptErrorTimeout <= 0L )break ; |
| 799 | _log.warn( "Waiting "+_acceptErrorTimeout+" msecs"); |
| 800 | try{ |
| 801 | Thread.sleep(_acceptErrorTimeout) ; |
| 802 | }catch(InterruptedException ee ){ |
| 803 | _log.warn("Recovery halt interrupted"); |
| 804 | break ; |
| 805 | } |
| 806 | _log.warn( "Resuming listener"); |
| 807 | try{ |
| 808 | |
| 809 | openPort() ; |
| 810 | |
| 811 | }catch(Exception ee ){ |
| 812 | _log.warn( "openPort reported : "+ee ) ; |
| 813 | _log.warn( "Waiting "+_acceptErrorTimeout+" msecs"); |
| 814 | try{ |
| 815 | Thread.sleep(_acceptErrorTimeout) ; |
| 816 | }catch(InterruptedException eee ){ |
| 817 | _log.warn("Recovery halt interrupted"); |
| 818 | break ; |
| 819 | } |
| 820 | } |
| 821 | } |
| 822 | |
| 823 | } |
| 824 | _log.info( "Listen thread finished"); |
| 825 | } |
| 826 | public class ShutdownEngine implements Runnable { |
| 827 | private Socket _socket = null ; |
| 828 | public ShutdownEngine( Socket socket ){ |
| 829 | _socket = socket ; |
| 830 | _nucleus.newThread( this , "Shutdown" ).start() ; |
| 831 | } |
| 832 | public void run(){ |
| 833 | InputStream inputStream = null ; |
| 834 | OutputStream outputStream = null ; |
| 835 | try{ |
| 836 | inputStream = _socket.getInputStream() ; |
| 837 | outputStream = _socket.getOutputStream() ; |
| 838 | outputStream.close() ; |
| 839 | byte [] buffer = new byte[1024] ; |
| 840 | /* |
| 841 | * eat the outstanding date from socket and close it |
| 842 | */ |
| 843 | while( inputStream.read(buffer,0,buffer.length) > 0 ) ; |
| 844 | inputStream.close() ; |
| 845 | }catch(Exception ee ){ |
| 846 | _log.warn("Shutdown : "+ee.getMessage() ) ; |
| 847 | }finally{ |
| 848 | try { |
| 849 | if( _logSocketIO.isDebugEnabled() ) { |
| 850 | _logSocketIO.debug("Socket CLOSE (ACCEPT) remote = " + _socket.getInetAddress() + ":" + _socket.getPort() + |
| 851 | " local = " +_socket.getLocalAddress() + ":" + _socket.getLocalPort() ); |
| 852 | } |
| 853 | _socket.close() ; |
| 854 | } catch (IOException e) { |
| 855 | // ignore |
| 856 | } |
| 857 | } |
| 858 | |
| 859 | _log.info( "Shutdown : done"); |
| 860 | } |
| 861 | } |
| 862 | public synchronized void shutdown(){ |
| 863 | |
| 864 | _log.info("Listen thread shutdown requested") ; |
| 865 | // |
| 866 | // it is still hard to stop an Pending I/O call. |
| 867 | // |
| 868 | if( _shutdown || ( _serverSocket == null ) )return ; |
| 869 | _shutdown = true ; |
| 870 | |
| 871 | try{ |
| 872 | if( _logSocketIO.isDebugEnabled() ) { |
| 873 | _logSocketIO.debug("Socket SHUTDOWN local = " + _serverSocket.getInetAddress() + ":" + _serverSocket.getLocalPort() ); |
| 874 | } |
| 875 | _serverSocket.close() ; } |
| 876 | catch(Exception ee){ |
| 877 | _log.warn( "ServerSocket close : "+ee ) ; |
| 878 | } |
| 879 | |
| 880 | if (_serverSocket.getChannel() == null) { |
| 881 | _log.info("Using faked connect to shutdown listen port"); |
| 882 | try { |
| 883 | new Socket("localhost", _listenPort).close(); |
| 884 | } catch (Exception e) { |
| 885 | _log.warn("ServerSocket faked connect : " + e.getMessage()); |
| 886 | } |
| 887 | } |
| 888 | |
| 889 | _this.interrupt() ; |
| 890 | |
| 891 | _log.info("Shutdown sequence done"); |
| 892 | } |
| 893 | public synchronized void open(){ |
| 894 | |
| 895 | } |
| 896 | public synchronized void close(){ |
| 897 | |
| 898 | } |
| 899 | } |
| 900 | private class RunEngineThread implements Runnable { |
| 901 | private Socket _socket = null ; |
| 902 | private RunEngineThread( Socket socket ){ |
| 903 | _socket = socket ; |
| 904 | } |
| 905 | public void run(){ |
| 906 | Thread t = Thread.currentThread() ; |
| 907 | try{ |
| 908 | _log.info( "acceptThread ("+t+"): creating protocol engine" ) ; |
| 909 | |
| 910 | Object [] argList = new Object[2] ; |
| 911 | Object auth = null ; |
| 912 | if( _authConstructor != null ){ |
| 913 | argList[0] = _nucleus ; |
| 914 | argList[1] = getArgs().clone() ; |
| 915 | auth = _authConstructor.newInstance( argList ) ; |
| 916 | } |
| 917 | StreamEngine engine = null ; |
| 918 | if( _protocol.equals( "ssh" ) ){ |
| 919 | engine = new SshStreamEngine( _socket , (SshServerAuthentication)auth ) ; |
| 920 | }else if( _protocol.equals( "raw" ) ){ |
| 921 | engine = new DummyStreamEngine( _socket ) ; |
| 922 | }else if( _protocol.equals( "telnet" ) ){ |
| 923 | engine = new TelnetStreamEngine( _socket , (TelnetServerAuthentication)auth ) ; |
| 924 | } |
| 925 | String userName = Subjects.getDisplayName(engine.getSubject()); |
| 926 | _log.info( "acceptThread ("+t+"): connection created for user "+userName ) ; |
| 927 | Object [] args ; |
| 928 | // |
| 929 | // |
| 930 | int p = userName.indexOf('@'); |
| 931 | |
| 932 | if( p > -1 )userName = p == 0 ? "unknown" : userName.substring(0,p); |
| 933 | |
| 934 | if( _loginConType == 0 ){ |
| 935 | args = new Object[2] ; |
| 936 | args[0] = getCellName()+"-"+userName+"*" ; |
| 937 | args[1] = engine ; |
| 938 | }else{ |
| 939 | args = new Object[3] ; |
| 940 | args[0] = getCellName()+"-"+userName+"*" ; |
| 941 | args[1] = engine ; |
| 942 | args[2] = getArgs().clone() ; |
| 943 | } |
| 944 | |
| 945 | Object cell = _loginConstructor.newInstance( args ) ; |
| 946 | if( _loginPrintMethod != null ){ |
| 947 | try{ |
| 948 | Object [] a = new Object[1] ; |
| 949 | a[0] = Integer.valueOf( _nucleus.getPrintoutLevel() ) ; |
| 950 | _loginPrintMethod.invoke( cell , a ) ; |
| 951 | }catch( Exception eee ){ |
| 952 | _log.warn( "Can't setPritoutLevel of " +args[0] ) ; |
| 953 | } |
| 954 | } |
| 955 | if( _maxLogin > -1 ){ |
| 956 | try{ |
| 957 | Method m = cell.getClass().getMethod( "getCellName" , new Class[0] ) ; |
| 958 | String cellName = (String)m.invoke( cell , new Object[0] ) ; |
| 959 | _log.info("Invoked cell name : "+cellName ) ; |
| 960 | synchronized( _childHash ){ |
| 961 | |
| 962 | /* |
| 963 | * while cell may be already gone do following trick: |
| 964 | * if put return an old cell, then it's a dead cell and we |
| 965 | * have to remove it. Dead cell is inserted by cleanup procedure: |
| 966 | * if a remove for non existing cells issued, then cells is dead, and |
| 967 | * we put it into _childHash. |
| 968 | */ |
| 969 | |
| 970 | Object deadCell = _childHash.put(cellName,cell) ; |
| 971 | if(deadCell != null ) { |
| 972 | _childHash.remove(cellName); |
| 973 | _log.warn("Cell died, removing " + cellName) ; |
| 974 | } |
| 975 | |
| 976 | childrenCounterChanged() ; |
| 977 | } |
| 978 | }catch( Exception ee ){ |
| 979 | _log.warn("Can't determine child name " + ee, ee) ; |
| 980 | } |
| 981 | } |
| 982 | _loginCounter ++ ; |
| 983 | |
| 984 | }catch( Exception e ){ |
| 985 | try{ _socket.close() ; }catch(IOException ee ){/* dead any way....*/} |
| 986 | _log.warn( "Exception in secure protocol : "+e, e ) ; |
| 987 | _loginFailures ++ ; |
| 988 | synchronized( _childHash ){ _childCount -- ; } |
| 989 | } |
| 990 | |
| 991 | |
| 992 | } |
| 993 | } |
| 994 | private void childrenCounterChanged(){ |
| 995 | int children = _childHash.size() ; |
| 996 | _log.info( "New child count : "+children ) ; |
| 997 | if( _loginBrokerHandler != null ) |
| 998 | _loginBrokerHandler.loadChanged( children , _maxLogin ) ; |
| 999 | } |
| 1000 | public boolean validateUser( String userName , String password ){ |
| 1001 | String [] request = new String[5] ; |
| 1002 | |
| 1003 | request[0] = "request" ; |
| 1004 | request[1] = userName ; |
| 1005 | request[2] = "check-password" ; |
| 1006 | request[3] = userName ; |
| 1007 | request[4] = password ; |
| 1008 | |
| 1009 | try{ |
| 1010 | CellMessage msg = new CellMessage( new CellPath(_authenticator) , |
| 1011 | request ) ; |
| 1012 | |
| 1013 | msg = sendAndWait( msg , 10000 ) ; |
| 1014 | if( msg == null ) |
| 1015 | throw new |
| 1016 | Exception("Pam request timed out"); |
| 1017 | |
| 1018 | Object [] r = (Object [])msg.getMessageObject() ; |
| 1019 | |
| 1020 | return ((Boolean)r[5]).booleanValue() ; |
| 1021 | |
| 1022 | }catch(Exception ee){ |
| 1023 | _log.warn(ee.toString(), ee); |
| 1024 | return false ; |
| 1025 | } |
| 1026 | |
| 1027 | } |
| 1028 | } |