1
2
3
4
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
61 List commonAccounts = new ArrayList(accountsWithName);
62 commonAccounts.retainAll(accountsWithSurname);
63 if (commonAccounts.size() == 1) {
64
65
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
71
72
73
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
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
92
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
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
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
118 accountsWithName = (Collection) nameToAccount.get(surname.toLowerCase());
119 accountsWithSurname = (Collection) surnameToAccount.get(name.toLowerCase());
120 if ((accountsWithName != null) && (accountsWithSurname != null)) {
121
122 List commonAccounts = new ArrayList(accountsWithName);
123 commonAccounts.retainAll(accountsWithSurname);
124 if (commonAccounts.size() == 1) {
125
126
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 }