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