package diskCacheV111.admin ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.io.File;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import dmg.cells.nucleus.CellAdapter;
import dmg.cells.nucleus.CellMessage;
import dmg.cells.nucleus.CellNucleus;
import dmg.cells.services.login.Crypt;
import dmg.util.AgingHash;
import dmg.util.UserPasswords;
import org.dcache.util.Args;
/**
* PAM Authentification Cell.
*
*/
public class PAMAuthentificator extends CellAdapter {
private static final Logger _log =
LoggerFactory.getLogger(PAMAuthentificator.class);
private final int request_len = 5;
private final int response_len = 6;
private final CellNucleus _nucleus;
private final String _cellName;
private String _service;
private PAM_Auth _pam;
private ExecAuth _execAuth;
private final Args _args;
private Date _started = new Date() ;
private int _requestCount;
private int _badRequestCount;
private int _failedRequestCount;
private static final int USER_SERVICE_FILE = 1 ;
private static final int USER_SERVICE_NIS = 2 ;
private static final int USER_SERVICE_LDAP = 3 ;
private static final int USER_SERVICE_CLASS = 4 ;
private UserPasswords _sysPassword;
private UserPasswords _egPassword;
private UserMetaDataProvider _userServiceProvider;
private DirContext _userServiceNIS;
private UserPasswords _userServiceFile;
private int _userServiceType;
private final Crypt _crypt = new Crypt() ;
public PAMAuthentificator(String cellName, String args)
{
super(cellName, PAMAuthentificator.class.getName(), args);
_nucleus = getNucleus();
_cellName = cellName;
_args = getArgs();
}
@Override
protected void starting() throws Exception
{
// Usage: ... [-service=<login_service>]
// [-syspassword=</etc/password>]
// [-dcachepassword=<dCachePasswordFile>]
// [-users=[file:]<passwordFilePath>|nis:<nisserver>|ldap:<ldapserver>]
// [-provider=<nisProviderClass>]
// [-external=<binToCheckPam>]
// [-usepam]
//
_service = _args.getOpt("service");
if (_service == null) {
_service = "dcache";
_log.info("'service' not defined. Using '" + _service + "' as default service");
}
String tmp = _args.getOpt("external");
if (tmp == null) {
if ((tmp = _args.getOpt("syspassword")) != null) {
_sysPassword = new UserPasswords(new File(tmp));
_log.info("using as SystemPasswordfile : {}", tmp);
}
if ((tmp = _args.getOpt("dcachepassword")) != null) {
_egPassword = new UserPasswords(new File(tmp));
_log.info("using as dCachePassword : {}", tmp);
}
if ((tmp = _args.getOpt("usepam")) != null) {
_pam = new PAM_Auth(_service);
_log.info("using PAM mudule");
}
} else {
_execAuth = new ExecAuth(tmp);
}
if ((tmp = _args.getOpt("users")) != null) {
_log.info("using as userService : {}", tmp);
if (tmp.startsWith("nis:")) {
String provider = _args.getOpt("provider");
provider = provider == null ? "com.sun.jndi.nis.NISCtxFactory" : provider;
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, provider);
// String url = tmp.substring(tmp.indexOf(":")+1);
// url = "nis://nisserv6.desy.de/desy.afs" ;
env.put(Context.PROVIDER_URL, tmp);
try {
_userServiceNIS = new InitialDirContext(env);
} catch (NamingException ne) {
_log.warn("Can't InitialDirContext(env) " + ne);
throw ne;
}
_userServiceType = USER_SERVICE_NIS;
} else if (tmp.startsWith("ldap:")) {
throw new IllegalArgumentException("LDap not yet supported");
} else if (tmp.startsWith("file:")) {
_userServiceFile = new UserPasswords(new File(tmp.substring(5)));
_userServiceType = USER_SERVICE_FILE;
} else if (tmp.startsWith("class:")) {
_userServiceProvider = initUserServiceProvider(tmp.substring(6));
_userServiceType = USER_SERVICE_CLASS;
} else if ((!tmp.contains(":"))) {
_userServiceFile = new UserPasswords(new File(tmp));
_userServiceType = USER_SERVICE_FILE;
} else {
throw new IllegalArgumentException("Invalid user service provider : " + tmp);
}
}
useInterpreter(true);
}
private UserMetaDataProvider initUserServiceProvider(String className )
throws Exception {
Class<?>[] argClasses = { CellAdapter.class } ;
Object[] argObjects = { this } ;
Class<? extends UserMetaDataProvider> exec = Class.forName(className).asSubclass(UserMetaDataProvider.class);
Constructor<? extends UserMetaDataProvider> constructor = exec.getConstructor(argClasses) ;
UserMetaDataProvider provider = constructor.newInstance(argObjects);
addCommandListener(provider);
return provider ;
}
@Override
public void getInfo( PrintWriter pw ){
try{
pw.println("PAMAuthentificator");
pw.println(" Request Count : "+_requestCount ) ;
pw.println(" Bad Request Count : "+_badRequestCount ) ;
pw.println(" Failed Request Count : "+_failedRequestCount ) ;
pw.println(" PAM loaded : "+(_pam==null?"Not requested":(String.valueOf(_pam.pamOk()))) ) ;
pw.println(" System Password File : "+
(_sysPassword==null?"<null>":_sysPassword.toString()) ) ;
pw.println(" dCache Password File : "+
(_egPassword==null?"<null>":_egPassword.toString() )) ;
switch( _userServiceType ){
case USER_SERVICE_FILE :
pw.println(" user metadata File : "+
(_userServiceFile==null?"<null>":_userServiceFile.toString()) ) ;
break ;
case USER_SERVICE_NIS :
pw.println(" user metadata Nis : "+_userServiceNIS ) ;
break ;
case USER_SERVICE_LDAP :
pw.println(" user metadata LDAP : "+_userServiceNIS ) ;
break ;
case USER_SERVICE_CLASS :
pw.println(" user metadata Class : "+_userServiceProvider.getClass().getName() ) ;
pw.println(" user metadata Object : "+_userServiceProvider ) ;
break ;
}
}catch(Exception eee){
eee.printStackTrace() ;
}
}
private static final String DUMMY_ADMIN = "5t2Hw7lNqVock" ;
private void updatePassword(){
try{
if( _sysPassword != null ) {
_sysPassword.update();
}
}catch(Exception ee ){
_log.warn( "Updating failed : "+_sysPassword ) ;
}
try{
if( _egPassword != null ) {
_egPassword.update();
}
}catch(Exception ee ){
_log.warn( "Updating failed : "+_egPassword ) ;
}
}
private boolean matchPassword( String userName , String password ){
String pswd;
updatePassword() ;
try{
if( userName.equals("admin" ) ){
if( ( _sysPassword == null ) ||
( ( pswd = _sysPassword.getPassword(userName) ) == null ) ){
if( ( _egPassword == null ) ||
( ( pswd = _egPassword.getPassword(userName) ) == null ) ){
pswd = DUMMY_ADMIN ;
}
}
return _crypt.crypt( pswd , password ).equals(pswd) ;
}else{
if( ( _sysPassword == null ) ||
( ( pswd = _sysPassword.getPassword(userName) ) == null ) ){
if( ( _egPassword == null ) ||
( ( pswd = _egPassword.getPassword(userName) ) == null ) ){
return false ;
}
}
return _crypt.crypt( pswd , password ).equals(pswd) ;
}
}catch( Throwable t ){
_log.warn( "Found : "+t ) ;
}
return false ;
}
private boolean authenticate( String principal , String password ){
password = password.trim() ;
if( _pam != null ){
return _pam.authenticate( principal , password ) ;
}else if( _execAuth != null ){
try{
String result =
_execAuth.command( "check "+_service+
" "+principal+
" "+password ) ;
return result.equals("true") ;
}catch(Exception ee ){
_log.warn(ee.toString(), ee) ;
return false ;
}
}else{
return false ;
}
}
private boolean checkAccess( String principal , String password ){
boolean pamOk = false ;
try{
pamOk = authenticate( principal , password ) ;
}catch(Exception ee ){
_log.warn( "_pam.authorize : "+ee ) ;
}
if( ! pamOk ){
_log.info("pam _log.infos no to <"+principal+"> (switching to local)");
try{
return matchPassword( principal , password ) ;
}catch(Exception ee ){
_log.warn( "matchPassword : "+ee ) ;
}
}
return pamOk ;
}
@Override
public void messageArrived( CellMessage msg ){
Serializable obj = msg.getMessageObject() ;
Serializable answer;
try{
_log.info( "Message type : "+obj.getClass() ) ;
if( ( obj instanceof Object [] ) &&
( ((Object[])obj).length >= 3 ) &&
( ((Object[])obj)[0].equals("request") ) ){
Object [] request = (Object[])obj ;
String user = request[1] == null ?
"unknown" : (String)request[1] ;
String command = (String)request[2] ;
_log.info( ">"+command+"< request from "+user ) ;
try{
switch (command) {
case "check-password":
answer = acl_check_password(request);
break;
case "get-metainfo":
answer = getMetaInfo(request);
break;
default:
throw new Exception("Command not found : " + command);
}
}catch( Exception xe ){
throw new Exception( "Problem : "+xe ) ;
}
}else{
String r = "Illegal message object received from : "+
msg.getSourcePath() ;
_log.warn( r ) ;
throw new Exception( r ) ;
}
}catch(Exception iex ){
answer = iex ;
}
if( answer instanceof Object [] ) {
((Object[]) answer)[0] = "response";
}
msg.revertDirection() ;
msg.setMessageObject( answer ) ;
try{
sendMessage( msg ) ;
}catch( RuntimeException ioe ){
_log.warn( "Can't send acl_response : "+ioe, ioe ) ;
}
}
private Serializable getMetaInfo( Object [] request )throws Exception {
return _userServiceType == USER_SERVICE_FILE ?
acl_get_metainfo( request ) :
_userServiceType == USER_SERVICE_NIS ?
acl_get_metainfo_nis( request ) :
_userServiceType == USER_SERVICE_CLASS ?
acl_get_metainfo_class( request ) :
new IllegalArgumentException("Panix "+_userServiceType) ;
}
///////////////////////////////////////////////////////////////////////////
//
// r[0] : request
// r[1] : <requestor>
// r[2] : get-metainfo
// r[3] : <user>
// r[4] : <key>[,<key>[...]]
//
// checks : nothing
//
// response
//
// r[0] : response
// r[1] : <requestor>
// r[2] : get-metainfo
// r[3] : <user>
// r[4] : <key>[,<key>[...]]
// r[5] : <valueOfKey1>
// r[6] : <valueOfKey2>
// r[7] : ...
//
private Serializable
acl_get_metainfo( Object [] request )
throws Exception {
if( ( request.length < 5 ) ||
( request[3] == null ) ||
( request[4] == null ) ) {
throw new
IllegalArgumentException(
"Not enough or illegal arguments for 'get-metainfo'");
}
String userName = request[3].toString() ;
if( _userServiceFile == null ) {
throw new
IllegalArgumentException("User service not configured");
}
_userServiceFile.update() ;
String [] dict = _userServiceFile.getRecord(userName) ;
if( dict == null ) {
throw new
IllegalArgumentException(
"No such user : " + userName);
}
StringTokenizer st = new StringTokenizer( request[4].toString() , "," ) ;
List<String> result = new ArrayList<>() ;
while( st.hasMoreTokens() ){
String key = st.nextToken() ;
switch (key) {
case "uid":
result.add(dict.length > 2 ? dict[2] : null);
break;
case "gid":
result.add(dict.length > 3 ? dict[3] : null);
break;
case "home":
result.add(dict.length > 5 ? dict[5] : null);
break;
case "fqn":
result.add(dict.length > 4 ? dict[4] : null);
break;
case "shell":
result.add(dict.length > 6 ? dict[6] : null);
break;
default:
result.add(null);
break;
}
}
String [] r = new String[5+result.size()] ;
for( int i = 0 ; i < 5 ; i++ ) {
r[i] = (String) request[i];
}
for( int i = 5 ; i < r.length ; i++ ) {
r[i] = result.get(i - 5);
}
return r ;
}
private Serializable
acl_get_metainfo_class( Object [] request )
throws Exception {
if( ( request.length < 5 ) ||
( request[3] == null ) ||
( request[4] == null ) ) {
throw new
IllegalArgumentException(
"Not enough or illegal arguments for 'get-metainfo'");
}
String userName = request[3].toString() ;
String principal = request[1].toString() ; //VP
if( _userServiceProvider == null ) {
throw new
IllegalArgumentException("User service not configured");
}
List<String> attrList = new ArrayList<>() ;
StringTokenizer st = new StringTokenizer( request[4].toString() , "," ) ;
while( st.hasMoreTokens() ) {
attrList.add(st.nextToken());
}
//VP Map map = _userServiceProvider.getUserMetaData( userName , attrList ) ;
Map<String,String> map = _userServiceProvider.getUserMetaData( principal, userName , attrList ) ;
String [] r = new String[5+attrList.size()] ;
for( int i = 0 ; i < 5 ; i++ ) {
r[i] = (String) request[i];
}
for( int i = 5 ; i < r.length ; i++ ){
String t = map.get(attrList.get(i-5));
r[i] = t == null ? "Unknown" : t ;
}
return r ;
}
private static final long HASH_REFRESH = 4*3600*1000 ;
private final AgingHash _map = new AgingHash(400) ;
private static class UserRecord {
private final Attributes _userRecord;
private long _timestamp;
private UserRecord( Attributes userRecord ){
_userRecord = userRecord ;
_timestamp = System.currentTimeMillis() ;
}
}
private Serializable
acl_get_metainfo_nis( Object [] request )
throws Exception {
try{
if( ( request.length < 5 ) ||
( request[3] == null ) ||
( request[4] == null ) ) {
throw new
IllegalArgumentException(
"Not enough or illegal arguments for 'check-password'");
}
String userName = request[3].toString() ;
if( _userServiceNIS == null ) {
throw new
IllegalArgumentException("User 'nis' service not configured");
}
UserRecord record = (UserRecord)_map.get(userName);
Attributes answer;
if( ( record == null ) ||
( ( record._timestamp != 0 ) &&
( System.currentTimeMillis() - record._timestamp ) > HASH_REFRESH ) ){
answer = _userServiceNIS.getAttributes("system/passwd/"+userName);
if( answer.size() == 0 ) {
throw new
IllegalArgumentException("No such user : " + userName);
}
_map.put(userName , new UserRecord(answer)) ;
}else{
answer = record._userRecord ;
}
StringTokenizer st = new StringTokenizer( request[4].toString() , "," ) ;
List<Object> result = new ArrayList<>();
while( st.hasMoreTokens() ){
String key = st.nextToken() ;
switch (key) {
case "uid":
try {
result.add(answer.get("uidNumber").get());
} catch (Exception e) {
result.add(null);
}
break;
case "gid":
try {
result.add(answer.get("gidNumber").get());
} catch (Exception e) {
result.add(null);
}
break;
case "home":
try {
result.add(answer.get("homeDirectory").get());
} catch (Exception e) {
result.add(null);
}
break;
case "fqn":
try {
result.add(answer.get("gecos").get());
} catch (Exception e) {
result.add(null);
}
break;
case "shell":
try {
result.add(answer.get("loginShell").get());
} catch (Exception e) {
result.add(null);
}
break;
default:
result.add(null);
break;
}
}
String [] r = new String[5+result.size()] ;
for( int i = 0 ; i < 5 ; i++ ) {
r[i] = (String) request[i];
}
for( int i = 5 ; i < r.length ; i++ ) {
r[i] = (String) result.get(i - 5);
}
return r ;
}catch(Exception ee ){
ee.printStackTrace() ;
throw ee;
}
}
///////////////////////////////////////////////////////////////////////////
//
// r[0] : request
// r[1] : <anything>
// r[2] : check-password
// r[3] : <user>
// r[4] : <password>[plainText]
//
// checks : nothing
//
// response
//
// r[0] : response
// r[1] : <user>
// r[2] : check-password
// r[3] : <user>
// r[4] : <password>[plainText]
// r[5] : Boolean(true/false)
//
private Serializable
acl_check_password( Object [] request )
{
if( request.length < 5 ) {
throw new
IllegalArgumentException(
"Not enough arguments for 'check-password'");
}
Object [] response = new Object[6] ;
System.arraycopy(request, 0, response, 0, 5);
response[1] = request[3] ;
String userName = (String)request[3] ;
String password = (String)request[4] ;
response[5] = checkAccess(userName, password);
return response ;
}
//
// r[0] : request
// r[1] : <requestor>
// r[2] : get-metainfo
// r[3] : <user>
// r[4] : <key>[,<key>[...]]
//
public static final String hh_check_meta = "<user>" ;
public String ac_check_meta_$_1(Args args ) throws Exception {
Object [] request = new Object[5] ;
request[0] = "request" ;
request[1] = "nobody" ;
request[2] = "get-metainfo" ;
request[3] = args.argv(0);
request[4] = "uid,gid,home,shell,fqn";
Object answer = getMetaInfo( request ) ;
if( answer instanceof Exception ) {
throw (Exception) answer;
}
Object [] a = (Object [])answer ;
return " Uid : " + a[5] + "\n" +
" Gid : " + a[6] + "\n" +
" Home : " + a[7] + "\n" +
"Shell : " + a[8] + "\n" +
" Name : " + a[9] + "\n";
}
public static final String hh_check_auth = "<user> <password>" ;
public String ac_check_auth_$_2( Args args )
{
String user = args.argv(0) ;
String pwd = args.argv(1) ;
boolean result = checkAccess( user , pwd ) ;
return result ? "Authentication ok for user <"+user+">" :
"Authentication failed for user <"+user+">" ;
}
public static final String hh_user_map_ls = "# [-t]" ;
public String ac_user_map_ls( Args args ){
if( _map == null ) {
throw new
IllegalArgumentException("User map hash not needed");
}
Iterator<?> i = _map.keysIterator() ;
StringBuilder sb = new StringBuilder() ;
while( i.hasNext() ){
sb.append(i.next()).append("\n");
}
return sb.toString() ;
}
public static final String hh_user_map_remove = "<userName> # remove user from hash";
public String ac_user_map_remove_$_1(Args args ){
if( _map == null ) {
throw new
IllegalArgumentException("User map hash not needed");
}
if( _map.remove(args.argv(0)) == null ) {
throw new
IllegalArgumentException("User name not in cache : " + args
.argv(0));
}
return "";
}
public static final String hh_user_map_add = "<userName> <uid> <gid> [<home> [<shell>]]" ;
public String ac_user_map_add_$_3_5( Args args ){
String user = args.argv(0) ;
String uid = args.argv(1) ;
String gid = args.argv(2) ;
String home = args.argc() > 3 ? args.argv(3) : "/dev/home" ;
String shell = args.argc() > 4 ? args.argv(4) : "/bin/shell" ;
BasicAttributes attr = new BasicAttributes() ;
attr.put( "uidNumber" , uid ) ;
attr.put( "gidNumber" , gid ) ;
attr.put( "homeDirectory" , home ) ;
attr.put( "loginShell" , shell ) ;
attr.put( "gecos" , user ) ;
UserRecord ur = new UserRecord(attr);
ur._timestamp = 0L ;
_map.put( user , ur ) ;
return "" ;
}
public static final String hh_user_map_reset = "# clear user map hash" ;
public String ac_user_map_reset( Args args ){
if( _map == null ) {
throw new
IllegalArgumentException("User map hash not needed");
}
_map.clear() ;
return "" ;
}
} // End of PAMAuthentificator