Source code: jsd/ftp/server/ftp/usermanager/LdapUserManager.java
1 /*
2 * ----------------------------------------------------------------------------
3 * JStrangeDownloader and all accompanying source code files are
4 * Copyright (C) 2002 Dusty Davidson (dustyd@iastate.edu)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * ----------------------------------------------------------------------------
20 *
21 * Please see gpl.txt for the full text of the GNU General Public
22 * License.
23 */
24
25 package jsd.ftp.server.ftp.usermanager;
26
27 import java.io.File;
28 import java.util.Collection;
29 import java.util.ArrayList;
30 import java.util.Properties;
31 import java.util.Collections;
32 import javax.naming.NamingException;
33 import javax.naming.Context;
34 import javax.naming.NamingEnumeration;
35 import javax.naming.directory.Attribute;
36 import javax.naming.directory.BasicAttribute;
37 import javax.naming.directory.DirContext;
38 import javax.naming.directory.InitialDirContext;
39 import javax.naming.directory.Attributes;
40 import javax.naming.directory.BasicAttributes;
41 import javax.naming.directory.SearchResult;
42 import javax.naming.directory.ModificationItem;
43
44 import jsd.ftp.io.LogFile;
45 import jsd.ftp.server.ftp.FtpConfig;
46
47 /**
48 * Ldap based user manager class. Tested using Netscape Directory Server 4.1.
49 * The LDAP requires the password to be nonempty for simple authentication. So
50 * instead of using empty string password (""), we will be using single space (" ").
51 *
52 * @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
53 */
54 public
55 class LdapUserManager extends UserManager {
56
57
58 // LDAP attributes
59 private final static String LOGIN = "memberuid";
60 private final static String UID = "uid";
61 private final static String CN = "cn";
62 private final static String SN = "sn";
63 private final static String PASSWORD = "userpassword";
64 private final static String OBJ_CLASS = "objectclass";
65 private final static String ENABLE = "enableflag";
66 private final static String ROOT_DIR = "homedirectory";
67 private final static String WRITE_PERM = "writepermission";
68 private final static String IDLE_TIME = "idletime";
69 private final static String UP_RATE = "uploadrate";
70 private final static String DOWN_RATE = "downloadrate";
71
72 private final static String[] ALL_ATTRS = {
73 CN,
74 LOGIN,
75 ENABLE,
76 ROOT_DIR,
77 WRITE_PERM,
78 IDLE_TIME,
79 UP_RATE,
80 DOWN_RATE
81 };
82
83 private final static Attribute OBJCLASS_ATTR = new BasicAttribute(OBJ_CLASS, true);
84
85
86 // Currently we are using only one connection.
87 // This will be replaced by LDAP connection pool.
88 private DirContext mAdminContext;
89 private Properties mAdminEnv;
90 private String mstLdapRoot;
91
92
93 /**
94 * Instantiate <code>UserManager</code> implementation.
95 * Open LDAP connection pool.
96 */
97 public LdapUserManager(FtpConfig cfg) throws Exception {
98 super(cfg);
99
100 // get ldap parameters
101 String url = cfg.getProperty(FtpConfig.PREFIX + "ldap.url");
102 String ldapRoot = cfg.getProperty(FtpConfig.PREFIX + "ldap.root");
103 String admin = cfg.getProperty(FtpConfig.PREFIX + "ldap.admin");
104 String password = cfg.getProperty(FtpConfig.PREFIX + "ldap.password");
105 String auth = cfg.getProperty(FtpConfig.PREFIX + "ldap.authentication");
106
107 mAdminEnv = new Properties();
108 mAdminEnv.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
109 mAdminEnv.setProperty(Context.PROVIDER_URL, url);
110 mAdminEnv.setProperty(Context.SECURITY_AUTHENTICATION, auth);
111 mAdminEnv.setProperty(Context.SECURITY_PRINCIPAL, admin);
112 mAdminEnv.setProperty(Context.SECURITY_CREDENTIALS, password);
113
114 mAdminContext = new InitialDirContext(mAdminEnv);
115 mstLdapRoot = ldapRoot;
116 getConfig().getLogger().info("LDAP user manager opened.");
117 }
118
119
120 /**
121 * Get common name
122 */
123 private synchronized String getCommonName(String login) throws NamingException {
124 Attributes matchAttrs = new BasicAttributes(true);
125 matchAttrs.put(new BasicAttribute(LOGIN, login));
126 matchAttrs.put(OBJCLASS_ATTR);
127 NamingEnumeration answers = mAdminContext.search("ou=people," + mstLdapRoot, matchAttrs, ALL_ATTRS);
128
129 String cn = null;
130 if (answers.hasMore()) {
131 SearchResult sr = (SearchResult)answers.next();
132 cn = sr.getAttributes().get(CN).get().toString();
133 }
134 answers.close();
135 return cn;
136 }
137
138
139 /**
140 * Get all user names
141 */
142 public synchronized Collection getAllUserNames() {
143 ArrayList allUsers = new ArrayList();
144
145 try {
146 Attributes matchAttrs = new BasicAttributes(true);
147 matchAttrs.put(OBJCLASS_ATTR);
148 NamingEnumeration answers = mAdminContext.search("ou=people," + mstLdapRoot, matchAttrs, ALL_ATTRS);
149 while (answers.hasMore()) {
150 SearchResult sr = (SearchResult)answers.next();
151 String login = sr.getAttributes().get(LOGIN).get().toString();
152 allUsers.add(login);
153 }
154 }
155 catch(NamingException ex) {
156 getConfig().getLogger().error(ex);
157 }
158
159 Collections.sort(allUsers);
160 return allUsers;
161 }
162
163
164 /**
165 * Get user object.
166 */
167 public synchronized User getUserByName(String name) {
168 User user = null;
169
170 try {
171 Attributes matchAttrs = new BasicAttributes(true);
172 matchAttrs.put(new BasicAttribute(LOGIN, name));
173 matchAttrs.put(OBJCLASS_ATTR);
174 NamingEnumeration answers = mAdminContext.search("ou=people," + mstLdapRoot, matchAttrs, ALL_ATTRS);
175 if (answers.hasMore()) {
176 SearchResult sr = (SearchResult)answers.next();
177 Attributes attrs = sr.getAttributes();
178
179 user = new User();
180 user.setName(attrs.get(LOGIN).get().toString());
181 user.getVirtualDirectory().setRootDirectory(new File(attrs.get(ROOT_DIR).get().toString()));
182 user.setEnabled(Boolean.TRUE.toString().equals(attrs.get(ENABLE).get().toString()));
183 user.getVirtualDirectory().setWritePermission(Boolean.TRUE.toString().equals(attrs.get(WRITE_PERM).get().toString()));
184 user.setMaxIdleTime( Integer.parseInt(attrs.get(IDLE_TIME).get().toString()) );
185 user.setMaxUploadRate( Integer.parseInt(attrs.get(UP_RATE).get().toString()) );
186 user.setMaxDownloadRate( Integer.parseInt(attrs.get(DOWN_RATE).get().toString()) );
187 }
188 answers.close();
189 }
190 catch(NamingException ex) {
191 getConfig().getLogger().error(ex);
192 user = null;
193 }
194
195 return user;
196 }
197
198
199 /**
200 * User authentication.
201 */
202 public boolean authenticate(String name, String password) {
203
204 // empty password string is not allowed
205 if (password == null) {
206 password = " ";
207 }
208 if (password.equals("")) {
209 password = " ";
210 }
211
212 try {
213 String cn = getCommonName(name);
214 if (cn != null) {
215 Properties userProp = (Properties)mAdminEnv.clone();
216 String dn = CN + '=' + cn + ",ou=people," + mstLdapRoot;
217 userProp.setProperty(Context.SECURITY_PRINCIPAL, dn);
218 userProp.setProperty(Context.SECURITY_CREDENTIALS, password);
219 DirContext userContext = new InitialDirContext(userProp);
220 userContext.close();
221 return true;
222 }
223 }
224 catch(NamingException ex) {
225 }
226 return false;
227 }
228
229
230 /**
231 * Save user
232 */
233 public synchronized void save(User user) throws NamingException {
234 if (doesExist(user.getName())) {
235 update(user);
236 }
237 else {
238 add(user);
239 }
240 }
241
242
243 /**
244 * Add a new user
245 */
246 private synchronized void add(User user) throws NamingException {
247
248 // empty password is not allowed
249 if (user.getPassword() == null) {
250 user.setPassword(" ");
251 }
252 if (user.getPassword().equals("")) {
253 user.setPassword(" ");
254 }
255
256 String cn = user.getName() + "-" + System.currentTimeMillis();
257 String path = CN + '=' + cn + ",ou=people," + mstLdapRoot;
258
259 Attributes attrs = new BasicAttributes(true);
260 attrs.put(new BasicAttribute(LOGIN, user.getName()));
261 attrs.put(new BasicAttribute(UID, user.getName()));
262 attrs.put(new BasicAttribute(CN, cn));
263 attrs.put(new BasicAttribute(SN, user.getName()));
264 attrs.put(new BasicAttribute(PASSWORD, user.getPassword()));
265 attrs.put(OBJCLASS_ATTR);
266 attrs.put(new BasicAttribute(ENABLE, String.valueOf(user.getEnabled())));
267 attrs.put(new BasicAttribute(ROOT_DIR, user.getVirtualDirectory().getRootDirectory()));
268 attrs.put(new BasicAttribute(WRITE_PERM, String.valueOf(user.getVirtualDirectory().getWritePermission())));
269 attrs.put(new BasicAttribute(IDLE_TIME, String.valueOf(user.getMaxIdleTime())));
270 attrs.put(new BasicAttribute(UP_RATE, String.valueOf(user.getMaxUploadRate())));
271 attrs.put(new BasicAttribute(DOWN_RATE, String.valueOf(user.getMaxDownloadRate())));
272
273 mAdminContext.bind(path, null, attrs);
274 }
275
276
277 /**
278 * Update an existing user
279 */
280 private synchronized void update(User user) throws NamingException {
281 String cn = getCommonName(user.getName());
282 String dn = CN + '=' + cn + ",ou=people," + mstLdapRoot;
283 ArrayList mods = new ArrayList();
284
285 if (user.getPassword() != null) {
286 if (user.getPassword().equals("")) {
287 user.setPassword(" ");
288 }
289 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(PASSWORD, user.getPassword())));
290 }
291 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(ENABLE, String.valueOf(user.getEnabled()))));
292 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(ROOT_DIR, user.getVirtualDirectory().getRootDirectory())));
293 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(WRITE_PERM, String.valueOf(user.getVirtualDirectory().getWritePermission()))));
294 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(IDLE_TIME, String.valueOf(user.getMaxIdleTime()))));
295 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(UP_RATE, String.valueOf(user.getMaxUploadRate()))));
296 mods.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(DOWN_RATE, String.valueOf(user.getMaxDownloadRate()))));
297
298
299 ModificationItem modArr[] = new ModificationItem[mods.size()];
300 for(int i=0; i<modArr.length; i++) {
301 modArr[i] = (ModificationItem)mods.get(i);
302 }
303 mAdminContext.modifyAttributes(dn, modArr);
304 }
305
306
307 /**
308 * User existance check
309 */
310 public synchronized boolean doesExist(String name) {
311 String cn = null;
312 try {
313 cn = getCommonName(name);
314 }
315 catch(NamingException ex) {
316 getConfig().getLogger().error(ex);
317 }
318 return cn != null;
319 }
320
321
322 /**
323 * Delete user
324 */
325 public synchronized void delete(String userName) throws NamingException {
326 String cn = getCommonName(userName);
327 if (cn != null) {
328 String dn = CN + '=' + cn + ",ou=people," + mstLdapRoot;
329 mAdminContext.unbind(dn);
330 }
331 }
332
333
334 /**
335 * Close user manager
336 */
337 public synchronized void dispose() {
338 if (mAdminContext != null) {
339 try {
340 mAdminContext.close();
341 }
342 catch(NamingException ex) {
343 }
344 mAdminContext = null;
345 }
346 }
347
348 // static block
349 static {
350 OBJCLASS_ATTR.add("top");
351 OBJCLASS_ATTR.add("person");
352 OBJCLASS_ATTR.add("organizationalPerson");
353 OBJCLASS_ATTR.add("inetOrgPerson");
354 OBJCLASS_ATTR.add("ftpUsers");
355 }
356 }
357