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