View Javadoc

1   /*
2    * NisClient.java
3    *
4    * Created on March 25, 2004, 4:11 PM
5    */
6   
7   package gov.bnl.gums.account;
8   
9   import gov.bnl.gums.configuration.ReadWriteLock;
10  import gov.bnl.gums.GUMS;
11  
12  import java.io.PrintStream;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Date;
16  import java.util.Hashtable;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Properties;
21  
22  import javax.naming.NamingEnumeration;
23  import javax.naming.directory.Attribute;
24  import javax.naming.directory.Attributes;
25  import javax.naming.directory.DirContext;
26  import javax.naming.directory.InitialDirContext;
27  import javax.naming.directory.SearchResult;
28  
29  import org.apache.commons.collections.MultiHashMap;
30  import org.apache.commons.collections.MultiMap;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /** 
35   * Retrieves the map from the NIS server and provide a logic to match name and
36   * surname to an account.
37   *
38   * @author  Gabriele Carcassi, Jay Packard
39   */
40  public class NISClient {
41      private Log log = LogFactory.getLog(NISClient.class);
42      private Log resourceAdminLog = LogFactory.getLog(GUMS.resourceAdminLog);
43      private Map accountToGecos;
44      private Map accountToName;
45      private Map accountToSurname;
46      private String nisJndiUrl;
47      private MultiMap nameToAccount;
48      private MultiMap surnameToAccount;
49      private ReadWriteLock lock = new ReadWriteLock("nis");
50      private Date lastUpdate = null;
51      
52      /**
53       * Creates a new instance of NisClient
54       * 
55       * @param nisJndiUrl
56       */
57      public NISClient(String nisJndiUrl) {
58          this.nisJndiUrl = nisJndiUrl;
59      }
60      
61      /**
62       * Find an account based on name and surname
63       * 
64       * @param name
65       * @param surname
66       * @return
67       */
68      public String findAccount(String name, String surname) {
69          fillMaps();
70          lock.obtainReadLock();
71          try {
72          log.trace("NIS findAccount. Name: " + name + " - Surname: " + surname + " - NIS: " + nisJndiUrl);
73          Collection accountsWithName = (Collection) nameToAccount.get(name.toLowerCase());
74          Collection accountsWithSurname = (Collection) surnameToAccount.get(surname.toLowerCase());
75          log.trace("Account matching. Name: " + accountsWithName + "- Surname: " + accountsWithSurname);
76          if ((accountsWithName != null) && (accountsWithSurname != null)) {
77              // Accounts for both name and surname were found
78              List commonAccounts = new ArrayList(accountsWithName);
79              commonAccounts.retainAll(accountsWithSurname);
80              if (commonAccounts.size() == 1) {
81                  // Only one account matching both name and surname was found
82                  // Pretty likely is the correct one
83                  String account = (String) commonAccounts.get(0);
84                  log.trace("NIS account Name/Surname single match. Name: " + name + " - Surname: " + surname + " - account: " + account);
85                  return account;
86              } else if (commonAccounts.size() > 1) {
87                  // More than one account with the matching account has been found
88                  // It might be that only one account is really for the user, and the
89                  // other are group/system accounts
90                  // Check whether only one account contains the surname
91                  Iterator iter = commonAccounts.iterator();
92                  String matchingAccount = null;
93                  while (iter.hasNext()) {
94                      String account = (String) iter.next();
95                      if (account.indexOf(surname.toLowerCase()) != -1) {
96                          if (matchingAccount == null) {
97                              matchingAccount = account;
98                          } else {
99                              // Two accounts matched. Can't decide, return null.
100                             log.trace("NIS account Name/Surname multiple match, multiple account with surname." +
101                             " Name: " + name + " - Surname: " + surname + " - account: not defined");
102                             resourceAdminLog.warn("NIS mapping: couldn't find single match for surname='" + surname + "' name='" + name + "'. Undecided between " + commonAccounts);
103                             return null;
104                         }
105                     }
106                 }
107                 if (matchingAccount != null) {
108                     // Only one matching account was found. There is a chance
109                     // this is the right account
110                     log.trace("NIS account Name/Surname multiple match, single account with surname." +
111                     " Name: " + name + " - Surname: " + surname + " - account: " + matchingAccount);
112                     return matchingAccount;
113                 }
114                 // Can't decide which account is the correct one
115                 log.trace("NIS account Name/Surname multiple match, no account with surname." +
116                 " Name: " + name + " - Surname: " + surname + " - account: not defined");
117                 resourceAdminLog.warn("NIS mapping: couldn't find single match for surname='" + surname + "' name='" + name + "'. Undecided between " + commonAccounts);
118                 return null;
119             }
120             // Common Accounts has no items, disregarding the name
121         }
122         if (accountsWithSurname != null) {
123             if (accountsWithSurname.size() == 1) {
124                 String account = (String) accountsWithSurname.iterator().next();
125                 log.trace("NIS account Surname single match, no match on Name. Name: " + name + " - Surname: " + surname + " - account: " + account);
126                 return account;
127             } else {
128                 log.trace("NIS account Surname multiple match, no match on Name. Name: " + name + " - Surname: " + surname + " - account: undefined");
129                 resourceAdminLog.warn("NIS mapping: couldn't find single match for surname='" + surname + "' name='" + name + "'. Undecided between " + accountsWithSurname);
130                 return null;
131             }
132         }
133         log.trace("NIS account no match on Surname. Name: " + name + " - Surname: " + surname + " - account: undefined");
134         // Try reversing name and surname
135         accountsWithName = (Collection) nameToAccount.get(surname.toLowerCase());
136         accountsWithSurname = (Collection) surnameToAccount.get(name.toLowerCase());
137         if ((accountsWithName != null) && (accountsWithSurname != null)) {
138             // Accounts for both name and surname were found
139             List commonAccounts = new ArrayList(accountsWithName);
140             commonAccounts.retainAll(accountsWithSurname);
141             if (commonAccounts.size() == 1) {
142                 // Only one account matching both name and surname was found
143                 // Pretty likely is the correct one
144                 String account = (String) commonAccounts.get(0);
145                 log.trace("NIS account inverted Name/Surname single match. Name: " + surname + " - Surname: " + name + " - account: " + account);
146                 return account;
147             }
148         }
149         return null;
150         } finally {
151             lock.releaseReadLock();
152         }
153     }
154     
155     /**
156      * Creates a new instance of NisClient
157      * 
158      * @param out
159      */
160     public void printMaps(PrintStream out) {
161         fillMaps();
162         out.println("account to gecos map");
163         out.println("---------------------");
164         out.println();
165         Iterator accounts = accountToGecos.keySet().iterator();
166         while (accounts.hasNext()) {
167             String account = (String) accounts.next();
168             String gecos = (String) accountToGecos.get(account);
169             String name = (String) accountToName.get(account);
170             String surname = (String) accountToSurname.get(account);
171             out.print(account);
172             for (int n = account.length(); n < 15; n++) {
173                 out.print(' ');
174             }
175             out.print(gecos);
176             for (int n = gecos.length(); n < 30; n++) {
177                 out.print(' ');
178             }
179             out.print(name);
180             for (int n = name.length(); n < 15; n++) {
181                 out.print(' ');
182             }
183             out.println(surname);
184         }
185     }
186     
187     private void fillMaps() {
188         if (mapsExpired()) {
189             lock.obtainWriteLock();
190             try {
191             accountToSurname = new Hashtable();
192             accountToName = new Hashtable();
193             accountToGecos = new Hashtable();
194             nameToAccount = new MultiHashMap();
195             surnameToAccount = new MultiHashMap();
196             fillMaps(retrieveJndiProperties(), accountToGecos, accountToName, accountToSurname);
197             lastUpdate = new Date();
198             resourceAdminLog.info("NIS map refreshed for '" + nisJndiUrl + "'");
199             log.debug("NIS map refreshed for '" + nisJndiUrl + "'");
200             } finally {
201                 lock.releaseWriteLock();
202             }
203         }
204     }
205     private void fillMaps(Properties jndiProperties, Map accountToGecos,
206     Map accountToName, Map accountToSurname) {
207         int nTries = 5;
208         Exception lastException = null;
209         int i = 0;
210         for (; i < nTries; i++) {
211             log.debug("Attemp " + i + " to retrieve map for '" + nisJndiUrl + "'");
212             accountToGecos.clear();
213             accountToName.clear();
214             accountToSurname.clear();
215             try {
216                 DirContext jndiCtx = new InitialDirContext(jndiProperties);
217                 NamingEnumeration map = jndiCtx.search("system/passwd.byname", "(cn=*)", null);
218                 log.trace("Server responded");
219                 while (map.hasMore()) {
220                     SearchResult res = (SearchResult) map.next();
221                     Attributes atts = res.getAttributes();
222                     String account = (String) atts.get("cn").get();
223                     Attribute gecosAtt = atts.get("gecos");
224                     if (gecosAtt != null) {
225                         String gecos = gecosAtt.get().toString();
226                         String name = retrieveName(gecos);
227                         String surname = retrieveSurname(gecos);
228                         log.trace("Adding user '" + account + "': GECOS='" + gecos + "' name='" + name + "' surname='" + surname + "'");
229                         accountToGecos.put(account, gecos);
230                         if (name != null) {
231                             accountToName.put(account, name);
232                             nameToAccount.put(name.toLowerCase(), account);
233                         }
234                         accountToSurname.put(account, surname);
235                         surnameToAccount.put(surname.toLowerCase(), account);
236                     } else {
237                         log.trace("Found user '" + account + "' with no GECOS field");
238                     }
239                 }
240                 jndiCtx.close();
241                 return;
242             } catch (javax.naming.NamingException ne) {
243                 log.warn("Error filling the maps for NIS "+nisJndiUrl, ne);
244                 lastException = ne;
245                 try {
246                     Thread.sleep(100);
247                 } catch (InterruptedException e) {
248                     log.warn("Interrupted", e);
249                 }
250             } catch (Exception e) {
251                 log.warn("Error filling the maps for NIS "+nisJndiUrl, e);
252                 lastException = e;
253                 try {
254                     Thread.sleep(100);
255                 } catch (InterruptedException ie) {
256                     log.warn("Interrupted", e);
257                 }
258             }
259         }
260         if (i == nTries) {
261             throw new RuntimeException("Couldn't retrieve NIS maps from " + nisJndiUrl, lastException);
262         }
263     }
264     
265     private boolean mapsExpired() {
266         if (lastUpdate == null) return true;
267         if ((System.currentTimeMillis() - lastUpdate.getTime()) > 60*60*1000) return true;
268         return false;
269     }
270     
271     private void printMap(PrintStream out, Map map, int offset) {
272         Iterator accounts = map.keySet().iterator();
273         while (accounts.hasNext()) {
274             String account = (String) accounts.next();
275             String gecos = (String) map.get(account);
276             out.print(account);
277             for (int n = account.length(); n < offset; n++) {
278                 out.print(' ');
279             }
280             out.println(gecos);
281             
282         }
283     }
284     
285     private Properties retrieveJndiProperties() {
286         Properties jndiProperties = new java.util.Properties();
287         jndiProperties.put("java.naming.provider.url", nisJndiUrl);
288         jndiProperties.put("java.naming.factory.initial","com.sun.jndi.nis.NISCtxFactory");
289         return jndiProperties;
290     }
291     
292     private String retrieveName(String gecos) {
293         gecos = gecos.trim();
294         int comma = gecos.indexOf(',');
295         if (comma != -1) {
296             gecos = gecos.substring(0, comma);
297         }
298         int index = gecos.lastIndexOf(' ');
299         if (index == -1) return "";
300         return gecos.substring(0, gecos.indexOf(' '));
301     }
302     
303     private String retrieveSurname(String gecos) {
304         gecos = gecos.trim();
305         int comma = gecos.indexOf(',');
306         if (comma != -1) {
307             gecos = gecos.substring(0, comma);
308         }
309         int index = gecos.lastIndexOf(' ');
310         if (index == -1) return gecos;
311         return gecos.substring(gecos.lastIndexOf(' ')+1);
312     }
313     
314 }