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