1
2
3
4
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
35
36
37
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
53
54
55
56 public NISClient(String nisJndiUrl) {
57 this.nisJndiUrl = nisJndiUrl;
58 }
59
60
61
62
63
64
65
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
77 List commonAccounts = new ArrayList(accountsWithName);
78 commonAccounts.retainAll(accountsWithSurname);
79 if (commonAccounts.size() == 1) {
80
81
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
87
88
89
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
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
108
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
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
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
134 accountsWithName = (Collection) nameToAccount.get(surname.toLowerCase());
135 accountsWithSurname = (Collection) surnameToAccount.get(name.toLowerCase());
136 if ((accountsWithName != null) && (accountsWithSurname != null)) {
137
138 List commonAccounts = new ArrayList(accountsWithName);
139 commonAccounts.retainAll(accountsWithSurname);
140 if (commonAccounts.size() == 1) {
141
142
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
156
157
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 }