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.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34
35
36
37
38
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
54
55
56
57 public NISClient(String nisJndiUrl) {
58 this.nisJndiUrl = nisJndiUrl;
59 }
60
61
62
63
64
65
66
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
78 List commonAccounts = new ArrayList(accountsWithName);
79 commonAccounts.retainAll(accountsWithSurname);
80 if (commonAccounts.size() == 1) {
81
82
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
88
89
90
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
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
109
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
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
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
135 accountsWithName = (Collection) nameToAccount.get(surname.toLowerCase());
136 accountsWithSurname = (Collection) surnameToAccount.get(name.toLowerCase());
137 if ((accountsWithName != null) && (accountsWithSurname != null)) {
138
139 List commonAccounts = new ArrayList(accountsWithName);
140 commonAccounts.retainAll(accountsWithSurname);
141 if (commonAccounts.size() == 1) {
142
143
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
157
158
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 }