1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.userrepository;
23
24 import org.apache.avalon.framework.activity.Initializable;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.james.api.user.User;
29 import org.apache.james.impl.jamesuser.AbstractUsersRepository;
30 import org.apache.james.impl.user.DefaultUser;
31
32 import javax.naming.AuthenticationException;
33 import javax.naming.NamingEnumeration;
34 import javax.naming.NamingException;
35 import javax.naming.directory.Attribute;
36 import javax.naming.directory.Attributes;
37 import javax.naming.directory.BasicAttribute;
38 import javax.naming.directory.BasicAttributes;
39 import javax.naming.directory.DirContext;
40 import javax.naming.directory.InitialDirContext;
41 import javax.naming.directory.ModificationItem;
42 import javax.naming.directory.SearchControls;
43 import javax.naming.directory.SearchResult;
44
45 import java.util.ArrayList;
46 import java.util.Hashtable;
47 import java.util.Iterator;
48 import java.util.List;
49
50
51
52
53
54
55
56
57
58
59 public class UsersLDAPRepository
60 extends AbstractUsersRepository
61 implements Configurable, Initializable{
62
63 private DirContext ctx;
64
65 private String LDAPHost;
66 private String rootNodeDN;
67 private String rootURL;
68 private String serverRDN;
69 private String baseNodeDN;
70 private String baseURL;
71 private String mailAddressAttr;
72 private String identAttr;
73 private String authType;
74 private String principal;
75 private String password;
76 private String usersDomain;
77 private String membersAttr;
78 private boolean manageGroupAttr;
79 private String groupAttr;
80 private boolean managePasswordAttr;
81 private String passwordAttr;
82 private SearchControls searchControls = new SearchControls();
83
84
85
86
87 public void configure(Configuration conf) throws ConfigurationException {
88 super.configure(conf);
89
90
91 LDAPHost = conf.getChild("LDAPServer").getValue();
92 usersDomain = conf.getChild("domain").getValue("localhost");
93 rootNodeDN = conf.getChild("LDAPRoot").getValue();
94 serverRDN = conf.getChild("ThisServerRDN").getValue();
95 mailAddressAttr
96 = conf.getChild("MailAddressAttribute").getValue();
97 identAttr = conf.getChild("IdentityAttribute").getValue();
98 authType = conf.getChild("AuthenticationType").getValue();
99 principal = conf.getChild("Principal").getValue();
100 password = conf.getChild("Password").getValue();
101
102 membersAttr = conf.getChild("MembersAttribute").getValue();
103 manageGroupAttr = conf.getChild("ManageGroupAttribute").getValueAsBoolean( false );
104
105
106 if (manageGroupAttr == true) {
107 groupAttr = conf.getChild("GroupAttribute").getValue();
108 }
109
110 managePasswordAttr = conf.getChild("ManagePasswordAttribute").getValueAsBoolean( false );
111
112
113 if (managePasswordAttr) {
114 passwordAttr = conf.getChild("PasswordAttribute").getValue();
115 }
116 }
117
118 public void setServerRoot() {
119 StringBuffer serverRootBuffer =
120 new StringBuffer(128)
121 .append(serverRDN)
122 .append(", ")
123 .append(rootNodeDN);
124 this.setBase(serverRootBuffer.toString());
125 }
126
127 public void setBase(String base) {
128 baseNodeDN = base;
129 }
130
131
132
133
134 public void initialize() throws Exception {
135
136 StringBuffer urlBuffer =
137 new StringBuffer(128)
138 .append(LDAPHost)
139 .append("/");
140 rootURL = urlBuffer.toString() + rootNodeDN;
141 baseURL = urlBuffer.toString();
142
143 getLogger().info("Creating initial context from " + baseURL);
144
145 Hashtable env = new Hashtable();
146 env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
147 "com.sun.jndi.ldap.LdapCtxFactory");
148 env.put(javax.naming.Context.PROVIDER_URL, baseURL);
149
150 env.put(javax.naming.Context.SECURITY_AUTHENTICATION, authType);
151 env.put(javax.naming.Context.SECURITY_PRINCIPAL, principal);
152 env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);
153
154 searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
155
156
157 ctx = new InitialDirContext(env);
158
159
160 getLogger().info("Initial context initialized from " + baseURL);
161 }
162
163
164
165 public String getChildDestination(String childName) {
166
167 String destination = null;
168 String filter = "cn=" + childName;
169 SearchControls ctls = new SearchControls();
170
171 try {
172
173 NamingEnumeration result = ctx.search("", filter, ctls);
174
175 if (result.hasMore()) {
176 StringBuffer destinationBuffer =
177 new StringBuffer(128)
178 .append("cn=")
179 .append(childName)
180 .append(", ")
181 .append(baseNodeDN);
182 destination = destinationBuffer.toString();
183 getLogger().info("Pre-exisisting LDAP node: " + destination);
184 } else {
185 Attributes attrs = new BasicAttributes(true);
186 Attribute objclass = new BasicAttribute("objectclass");
187 objclass.add("top");
188 objclass.add("rfc822MailGroup");
189 attrs.put(objclass);
190 Attribute cname = new BasicAttribute("cn");
191 cname.add(childName);
192 attrs.put(cname);
193 Attribute owner = new BasicAttribute("owner");
194 owner.add("JAMES-unassigned");
195 attrs.put(owner);
196
197
198
199
200
201 ctx.createSubcontext("cn=" + childName, attrs);
202
203
204 StringBuffer destinationBuffer =
205 new StringBuffer(128)
206 .append("cn=")
207 .append(childName)
208 .append(", ")
209 .append(baseNodeDN);
210 destination = destinationBuffer.toString();
211 getLogger().info("Created new LDAP node: " + destination);
212 }
213 } catch (NamingException e) {
214 getLogger().error("Problem with child nodes " + e.getMessage(), e);
215 }
216
217 return destination;
218 }
219
220
221
222
223
224
225 public Iterator list() {
226 return getUsers().iterator();
227 }
228
229
230
231
232
233
234
235 public boolean addUser(User user) {
236 return false;
237 }
238
239
240
241
242 public User getUserByName(String name) {
243 return new DefaultUser("dummy", "dummy");
244 }
245
246
247
248
249 public User getUserByNameCaseInsensitive(String name) {
250 return getUserByName(name);
251 }
252
253
254
255
256 public boolean containsCaseInsensitive(String name) {
257 return getUsers().contains(name);
258 }
259
260
261
262
263 public String getRealName(String name) {
264 return getRealName(name, ignoreCase);
265 }
266
267
268
269
270 public String getRealName(String name, boolean ignoreCase) {
271 Iterator it = list();
272 while (it.hasNext()) {
273 String temp = (String) it.next();
274 if (ignoreCase) {
275 if (name.equalsIgnoreCase(temp)) {
276 return temp;
277 }
278 } else {
279 if (name.equals(temp)) {
280 return temp;
281 }
282 }
283 }
284 return null;
285 }
286
287
288
289
290 public boolean updateUser(User user) {
291 return false;
292 }
293
294
295
296
297 public boolean addUser(String username, String password) {
298 if (!contains(username)) {
299 addUser(username, password);
300 return contains(username);
301 } else {
302 return false;
303 }
304 }
305
306 private void addGroupToUser(String userName) {
307 String[] attrIDs = {membersAttr};
308
309 Hashtable env = new Hashtable();
310 env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
311 env.put(javax.naming.Context.PROVIDER_URL, rootURL);
312
313 DirContext rootCtx = null;
314 try {
315 rootCtx = new InitialDirContext(env);
316
317 String[] returnAttrs = {groupAttr};
318 SearchControls ctls = new SearchControls();
319 ctls.setReturningAttributes(attrIDs);
320 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
321 StringBuffer filterBuffer =
322 new StringBuffer(128)
323 .append(mailAddressAttr)
324 .append("=")
325 .append(userName)
326 .append("@")
327 .append(usersDomain);
328 String filter = filterBuffer.toString();
329
330 NamingEnumeration enumeration = rootCtx.search("", filter, ctls);
331
332 if (enumeration.hasMore()) {
333 SearchResult newSr = (SearchResult)enumeration.next();
334 String userDN = newSr.getName();
335 Attribute servers = rootCtx.getAttributes(userDN, returnAttrs).get(groupAttr);
336
337
338 if (servers != null && servers.contains(baseNodeDN)) {
339 getLogger().info(baseNodeDN + " already in user's Groups. " );
340
341
342 } else {
343
344
345
346
347
348 rootCtx.modifyAttributes(userDN, DirContext.ADD_ATTRIBUTE, new BasicAttributes(groupAttr, baseNodeDN, true));
349
350
351 getLogger().info(baseNodeDN + " added to user's groups ");
352
353
354 }
355
356 } else {
357 StringBuffer infoBuffer =
358 new StringBuffer(64)
359 .append("User ")
360 .append(userName)
361 .append(" not in directory.");
362 getLogger().info(infoBuffer.toString());
363
364
365 }
366 } catch (NamingException e) {
367 getLogger().error("Problem adding group to user " + userName);
368
369
370
371 } finally {
372 closeDirContext(rootCtx);
373 }
374 }
375
376
377
378
379 public synchronized void removeUser(String userName) {
380 String[] attrIDs = {membersAttr};
381
382 try {
383 Attribute members = ctx.getAttributes("", attrIDs).get(membersAttr);
384 if (members == null) {
385 System.out.println("UsersLDAPRepository - Null list attribute.");
386
387 } else if (!members.contains(userName)) {
388 getLogger().info(userName + " missing from mailGroup. ");
389
390
391 } else {
392
393
394
395
396
397
398 ModificationItem[] mods = new ModificationItem[1];
399 mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(membersAttr, userName));
400
401 ctx.modifyAttributes("", mods);
402
403
404
405 getLogger().info(userName + " removed from mailGroup. ");
406
407 }
408 } catch (NamingException e) {
409 StringBuffer exceptionBuffer =
410 new StringBuffer(256)
411 .append("Problem removing user ")
412 .append(userName)
413 .append(": ")
414 .append(e);
415 getLogger().error(exceptionBuffer.toString());
416
417
418
419 }
420 if (manageGroupAttr) {
421 removeGroupFromUser(userName);
422 }
423
424 if (managePasswordAttr) {
425
426 }
427
428 }
429
430 public void removeGroupFromUser(String userName) {
431
432 Hashtable env = new Hashtable();
433 env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
434 env.put(javax.naming.Context.PROVIDER_URL, rootURL);
435
436
437 DirContext rootCtx = null;
438 try {
439 rootCtx = new InitialDirContext(env);
440
441
442 String[] returnAttrs = {groupAttr};
443 SearchControls ctls = new SearchControls();
444 ctls.setReturningAttributes(returnAttrs);
445 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
446 StringBuffer filterBuffer =
447 new StringBuffer(128)
448 .append(mailAddressAttr)
449 .append("=")
450 .append(userName)
451 .append("@")
452 .append(usersDomain);
453 String filter = filterBuffer.toString();
454
455 NamingEnumeration enumeration = rootCtx.search("", filter, ctls);
456
457 if (enumeration.hasMore()) {
458 SearchResult newSr = (SearchResult)enumeration.next();
459 String userDN = newSr.getName();
460
461 System.out.println("Found user entry: " + userDN);
462
463 Attribute servers = rootCtx.getAttributes(userDN, returnAttrs).get(groupAttr);
464 if (servers == null) {
465 getLogger().info("GroupAttribute missing from user: " + userName);
466
467
468 } else if (!servers.contains(baseNodeDN)) {
469 getLogger().info(baseNodeDN + " missing from users' Groups. " );
470
471
472 } else {
473
474
475
476
477
478 ModificationItem[] mods = new ModificationItem[1];
479 mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(groupAttr, baseNodeDN));
480
481 rootCtx.modifyAttributes(userDN, mods);
482
483
484
485
486 getLogger().info(baseNodeDN + " removed from users' groups " );
487
488
489 }
490
491 } else {
492 StringBuffer infoBuffer =
493 new StringBuffer(64)
494 .append("User ")
495 .append(userName)
496 .append(" not in directory.");
497 getLogger().info(infoBuffer.toString());
498
499
500 }
501 } catch (NamingException e) {
502 StringBuffer exceptionBuffer =
503 new StringBuffer(256)
504 .append("Problem removing user ")
505 .append(userName)
506 .append(e);
507 getLogger().error(exceptionBuffer.toString());
508
509
510
511 } finally {
512 closeDirContext(rootCtx);
513 rootCtx = null;
514 }
515 }
516
517
518
519
520
521 public boolean contains(String name) {
522 boolean found = false;
523 if (ignoreCase) {
524 if(containsCaseInsensitive(name)) {
525 found = true;
526 }
527 } else {
528 Iterator it = list();
529
530 while (it.hasNext()) {
531 if (name.equals(it.next())) {
532 found = true;
533 }
534 }
535 }
536
537 if(found) {
538 StringBuffer infoBuffer =
539 new StringBuffer(64)
540 .append("Found ")
541 .append(name)
542 .append(" in mailGroup. ");
543 getLogger().info(infoBuffer.toString());
544 return true;
545 }
546 return false;
547 }
548
549
550
551
552
553 public boolean test(String name, String testPassword) {
554 boolean result = false;
555 boolean foundFlag = false;
556 String userDN = null;
557
558 try {
559 String[] returnAttrs = {identAttr, passwordAttr};
560 SearchControls ctls = new SearchControls();
561 ctls.setReturningAttributes(returnAttrs);
562 ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
563 StringBuffer filterBuffer =
564 new StringBuffer(128)
565 .append(mailAddressAttr)
566 .append("=")
567 .append(name)
568 .append("@")
569 .append(usersDomain);
570 String filter = filterBuffer.toString();
571
572 Hashtable env = new Hashtable();
573 env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
574 env.put(javax.naming.Context.PROVIDER_URL, rootURL);
575 DirContext rootCtx = null;
576
577 try {
578 rootCtx = new InitialDirContext(env);
579
580 NamingEnumeration enumeration = rootCtx.search("", filter, ctls);
581 if (enumeration.hasMore()) {
582 SearchResult sr = (SearchResult)enumeration.next();
583 String userRDN = sr.getName();
584 StringBuffer userDNBuffer =
585 new StringBuffer(128)
586 .append(userRDN)
587 .append(", ")
588 .append(rootNodeDN);
589 userDN = userDNBuffer.toString();
590 foundFlag = true;
591
592 }
593 } finally {
594 closeDirContext(rootCtx);
595 }
596 } catch (Exception e) {
597 StringBuffer exceptionBuffer =
598 new StringBuffer(256)
599 .append("Problem finding user ")
600 .append(name)
601 .append(" for password test.")
602 .append(e);
603 getLogger().error(exceptionBuffer.toString());
604
605
606 }
607
608 if (foundFlag) {
609 Hashtable env2 = new Hashtable();
610 env2.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
611 env2.put(javax.naming.Context.PROVIDER_URL, rootURL);
612 env2.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
613 env2.put(javax.naming.Context.SECURITY_PRINCIPAL, userDN);
614 env2.put(javax.naming.Context.SECURITY_CREDENTIALS, testPassword);
615
616
617 DirContext testCtx = null;
618 try {
619 testCtx = new InitialDirContext(env2);
620 result = true;
621
622 } catch (AuthenticationException ae) {
623 result = false;
624 StringBuffer exceptionBuffer =
625 new StringBuffer(256)
626 .append("Attempt to authenticate with incorrect password for ")
627 .append(name)
628 .append(" : ")
629 .append(ae);
630 getLogger().error(exceptionBuffer.toString());
631
632
633
634 } catch (Exception e) {
635 StringBuffer exceptionBuffer =
636 new StringBuffer(256)
637 .append("Problem checking password for ")
638 .append(name)
639 .append(" : ")
640 .append(e);
641 getLogger().error(exceptionBuffer.toString());
642
643
644
645 } finally {
646 closeDirContext(testCtx);
647 }
648 }
649 return result;
650
651 }
652
653
654
655
656 public int countUsers() {
657 return getUsers().size();
658 }
659
660
661
662
663
664
665 public void dispose() throws Exception {
666 closeDirContext(ctx);
667 ctx = null;
668 }
669
670 private void closeDirContext(DirContext ctx) {
671 try {
672 if (ctx != null) {
673 ctx.close();
674 }
675 } catch (NamingException ne) {
676 getLogger().warn("UsersLDAPRepository: Unexpected exception encountered while closing directory context: " + ne);
677 }
678 }
679
680
681
682
683
684
685
686 protected void doAddUser(User user) {
687 String userName = user.getUserName();
688
689 String[] attrIDs = {membersAttr};
690
691
692
693 try {
694 Attribute members = ctx.getAttributes("", attrIDs).get(membersAttr);
695
696
697 if (members != null && members.contains(userName)) {
698 StringBuffer infoBuffer =
699 new StringBuffer(64)
700 .append("Found ")
701 .append(userName)
702 .append(" already in mailGroup. ");
703 getLogger().info(infoBuffer.toString());
704
705
706 } else {
707
708
709
710
711
712 ModificationItem[] mods = new ModificationItem[1];
713 mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(membersAttr, userName));
714
715 ctx.modifyAttributes("", mods);
716
717
718 StringBuffer infoBuffer =
719 new StringBuffer(128)
720 .append(userName)
721 .append(" added to mailGroup ")
722 .append(baseNodeDN);
723 getLogger().info(infoBuffer.toString());
724
725 }
726 } catch (NamingException e) {
727 StringBuffer exceptionBuffer =
728 new StringBuffer(256)
729 .append("Problem adding user ")
730 .append(userName)
731 .append(" to: ")
732 .append(baseNodeDN)
733 .append(e);
734 getLogger().error(exceptionBuffer.toString());
735 }
736
737
738
739 if (manageGroupAttr) {
740 addGroupToUser(userName);
741 }
742
743
744
745
746 }
747
748 protected void doUpdateUser(User user) {
749
750 }
751
752
753
754
755
756
757 private List getUsers() {
758 List result = new ArrayList();
759 String filter = mailAddressAttr + "=*";
760
761 try {
762 NamingEnumeration results = ctx.search(rootNodeDN, filter, searchControls);
763
764 while (results != null && results.hasMore()) {
765 Attributes members = ((SearchResult) results.next()).getAttributes();
766
767 if (members != null) {
768
769 Attribute attr = members.get(identAttr);
770 if (attr != null) {
771 NamingEnumeration e = attr.getAll();
772 while (e.hasMore()) {
773 result.add(e.next());
774 }
775 }
776 }
777 }
778 } catch (NamingException e) {
779 getLogger().error("Problem listing mailboxes. " + e );
780
781 }
782 return result;
783 }
784 }
785
786