View Javadoc

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