KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ejbca > samples > RemoteVerifyServlet


1 /*************************************************************************
2  * *
3  * EJBCA: The OpenSource Certificate Authority *
4  * *
5  * This software is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU Lesser General Public *
7  * License as published by the Free Software Foundation; either *
8  * version 2.1 of the License, or any later version. *
9  * *
10  * See terms of license at gnu.org. *
11  * *
12  *************************************************************************/

13  
14 package org.ejbca.samples;
15
16 import java.io.BufferedReader JavaDoc;
17 import java.io.IOException JavaDoc;
18 import java.io.InputStream JavaDoc;
19 import java.io.InputStreamReader JavaDoc;
20 import java.util.Date JavaDoc;
21 import java.util.Enumeration JavaDoc;
22 import java.util.Hashtable JavaDoc;
23 import java.util.Map JavaDoc;
24 import java.util.StringTokenizer JavaDoc;
25
26 import javax.servlet.ServletConfig JavaDoc;
27 import javax.servlet.ServletException JavaDoc;
28 import javax.servlet.ServletOutputStream JavaDoc;
29 import javax.servlet.http.HttpServlet JavaDoc;
30 import javax.servlet.http.HttpServletRequest JavaDoc;
31 import javax.servlet.http.HttpServletResponse JavaDoc;
32
33 import org.apache.log4j.Logger;
34 import org.ejbca.ui.web.RequestHelper;
35
36
37 /**
38  * Servlet to authenticate a user. Simple database using a file to keep users in format:
39  * instance;username;password;DN DN is in form: dn-c:dn-o:dn-ou:dn-ln:dn-gn:dn-cn where parts can
40  * be left out as desired. Expects these parameters when called: (error 500 if any missing)
41  *
42  * <ul>
43  * <li>
44  * user=&lt;username&gt;
45  * </li>
46  * <li>
47  * password=&lt;password&gt;
48  * </li>
49  * <li>
50  * version=&lt;major&gt;.&lt;minor&gt;
51  * </li>
52  * </ul>
53  *
54  * <p>
55  * Returns a logic token stating that user is authenticated followed by the information to use for
56  * this user's certificate.
57  * </p>
58  *
59  * @author Original code by Peter Neemeth
60  * @version $Id: RemoteVerifyServlet.java,v 1.2 2006/02/09 10:05:37 anatom Exp $
61  */

62 public class RemoteVerifyServlet extends HttpServlet JavaDoc {
63     private static Logger log = Logger.getLogger(RemoteVerifyServlet.class);
64
65     /** Status code for successful communication */
66     public static final String JavaDoc MSG_OK = "200 OK";
67
68     /** Status code for failed communication */
69     public static final String JavaDoc MSG_PROTOCOL_MISMATCH = "400 Wrong protocol version";
70
71     /** Status code for generic error */
72     public static final String JavaDoc MSG_GENERIC_ERROR = "500 ERROR (Missing parameter?) : ";
73
74     /** Name of user id parameter */
75     public static final String JavaDoc REQUEST_USERNAME = "username";
76
77     /** Name of password parameter */
78     public static final String JavaDoc REQUEST_PASSWORD = "password";
79
80     /** Name of version parameter */
81     public static final String JavaDoc REQUEST_VERSION = "version";
82
83     /** Token for protocol */
84     public static final String JavaDoc RESPONSE_END = "end";
85
86     /** Token for protocol */
87     public static final String JavaDoc RESPONSE_STATUS = "status";
88
89     /** Token for protocol */
90     public static final String JavaDoc RESPONSE_RESULT = "result";
91
92     /** Token for protocol */
93     public static final String JavaDoc RESPONSE_MESSAGE = "message";
94
95     /** Status code for granting of certificate. */
96     public static final String JavaDoc GRANT = "grant";
97
98     /** Status code for rejecting certificate request. */
99     public static final String JavaDoc REJECT = "reject";
100
101     /** Version of the protocol used when communicating back to requestor */
102     protected static final int PROTOCOL_VERSION_MAJOR = 1;
103
104     /** Version of the protocol used when communicating back to requestor */
105     protected static final int PROTOCOL_VERSION_MINOR = 0;
106
107     /**
108      * Basic structure containing users. Top level keyed on instance gives new Hashtable keyed on
109      * username with String[] = { password, result } as data.
110      */

111     protected static Hashtable JavaDoc users;
112
113     /**
114      * Delimiter between parts in DN
115      *
116      * <p>
117      * Can be controlled via properties file.
118      * </p>
119      */

120     protected static final String JavaDoc DNPART_DELIMITER = ":";
121
122     /**
123      * Separator between name and value in DN name = value
124      *
125      * <p>
126      * Can be controlled via properties file.
127      * </p>
128      */

129     protected static final String JavaDoc DNPART_NAME_VALUE_SEPARATOR = "=";
130
131     /**
132      * For easy export from Excel and others.
133      *
134      * <p>
135      * Can be controlled via properties file.
136      * </p>
137      */

138     protected static final String JavaDoc RECORD_SEPARATOR = ";";
139
140     /**
141      * Ignored lines in DBUSER_file start with this character.
142      *
143      * <p>
144      * Can be controlled via properties file.
145      * </p>
146      */

147     protected static final String JavaDoc LINE_COMMENT = ";";
148
149     /** What parameter to send when using GET to show status. */
150     protected static final String JavaDoc STATUS_KEY = "status";
151
152     /** Count total accesses */
153     protected static int countAccess = 0;
154
155     /** Count granted accesses */
156     protected static int countGranted = 0;
157
158     /** Count rejected accesses */
159     protected static int countRejected = 0;
160
161     /**
162      * Updates result with name-value-pairs extracted from dnPartsString
163      *
164      * @param result where the result is stuffed
165      * @param dnPartsString name-value-pairs separated by delimiter
166      */

167     void addUserDataToResult(AuthResult result, final String JavaDoc dnPartsString) {
168         if (dnPartsString == null) {
169             return;
170         }
171
172         Enumeration JavaDoc dnParts = new StringTokenizer JavaDoc(dnPartsString, DNPART_DELIMITER);
173
174         while (dnParts.hasMoreElements()) {
175             String JavaDoc dnPart = (String JavaDoc) dnParts.nextElement();
176             int separatorPosition = dnPart.indexOf(DNPART_NAME_VALUE_SEPARATOR);
177             String JavaDoc dnName = dnPart.substring(0, separatorPosition);
178             String JavaDoc dnValue = dnPart.substring(separatorPosition + 1); // skip separator
179
result.add(dnName, dnValue);
180             debugLog("addUserDataToResult: result=" + result);
181         }
182     }
183
184     /**
185      * Authenticate a user given a querystring. <b>This is the only method a customer should have
186      * to rewrite/override.</b>
187      *
188      * @param username containing parsed username from requestor
189      * @param password containing parsed password from requestor
190      *
191      * @return status + certificate contents in an AuthResult
192      */

193     protected AuthResult authenticateUser(String JavaDoc username, String JavaDoc password) {
194         AuthResult result = new AuthResult();
195         String JavaDoc[] userData = findUserData(username);
196
197         if (userData == null) {
198             result.reject();
199             result.setReason("Failed to authenticate credentials.");
200             debugLog("authenticateUser: No such user. REJECTING");
201         } else {
202             debugLog("authenticateUser: Got userData for user '" + username + "'");
203
204             if (password.equals(userData[0])) {
205                 debugLog("authenticateUser: Password matched. GRANTING");
206                 result.grant();
207                 addUserDataToResult(result, userData[1]);
208             } else {
209                 debugLog("authenticateUser: Password missmatch. REJECTING");
210                 result.reject();
211                 result.setReason("Failed to authenticate credentials.");
212             }
213         }
214
215         return result;
216     }
217
218     /**
219      * Logs extensively to the log.
220      *
221      * @param s What to log
222      */

223     protected void debugLog(final String JavaDoc s) {
224         log.debug(s);
225     }
226
227     /**
228      * logs info.
229      *
230      * @param s What to log
231      */

232     protected void infoLog(final String JavaDoc s) {
233         log.info(s);
234     }
235
236     /**
237      * logs error
238      *
239      * @param s What to log
240      */

241     protected void errorLog(final String JavaDoc s) {
242         log.error(s);
243     }
244
245     /**
246      * logs error and stacktrace.
247      *
248      * @param s What to log
249      * @param e DOCUMENT ME!
250      */

251     protected void errorLog(final String JavaDoc s, java.lang.Exception JavaDoc e) {
252         log.error(s, e);
253     }
254
255     /**
256      * Allows for checking status of.
257      *
258      * @param req javax.servlet.http.HttpServletRequest
259      * @param res javax.servlet.http.HttpServletResponse
260      *
261      * @exception javax.servlet.ServletException The exception description.
262      */

263     protected void doGet(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
264         throws ServletException JavaDoc, IOException JavaDoc {
265         res.setContentType("text/plain");
266
267         ServletOutputStream JavaDoc out = res.getOutputStream();
268
269         // Keep this for logging.
270
String JavaDoc remoteAddr = req.getRemoteAddr();
271
272         // Extract information about request type and how we were called.
273
// Also suitable for logging.
274
String JavaDoc method = req.getMethod();
275         String JavaDoc path = req.getServletPath();
276         log.debug("Received request: method="+method+", path="+path);
277
278         out.print("You called from " + remoteAddr);
279         out.println(" using " + method + " as method.");
280
281         RequestHelper.setDefaultCharacterEncoding(req);
282         try {
283             Map JavaDoc params = req.getParameterMap();
284
285             if (params.containsKey(STATUS_KEY)) {
286                 out.println("\n");
287                 out.println((new Date JavaDoc()).toString() + " RemoteVerify status: ");
288                 out.println("Accesses: " + countAccess);
289                 out.println("Granted: " + countGranted);
290                 out.println("Rejected: " + countRejected);
291
292                 if (users != null) {
293                     out.println("Number of users in database: " + users.size());
294                 } else {
295                     out.println("No users in database.");
296                 }
297
298                 out.println("\n");
299                 out.println("Protocol version: " + PROTOCOL_VERSION_MAJOR + "." +
300                     PROTOCOL_VERSION_MINOR);
301                 out.println("Database loaded from: " + getInitParameter("dbfilename"));
302                 out.println((new Date JavaDoc()).toString() + " DONE.");
303             }
304         } catch (IllegalArgumentException JavaDoc ignored) {
305             out.println("Couldn't parse that request. Check parameters and try again.");
306         }
307
308         out.println("Request done.");
309     }
310
311     /**
312      * Accepts requests and dispatches to authenticateUser in this object.
313      *
314      * <p>
315      * Returns one of the following cases. (Apart from status being the first line, order is not
316      * specified.)
317      *
318      * <ul>
319      * <li>
320      * A granted reply:
321      * <pre>
322      * status=200 OK
323      * result=grant
324      * dn-ou=OU
325      * dn-o=O
326      * dn-cn=CN
327      * end
328      * </pre>
329      * </li>
330      * <li>
331      * A rejected reply:
332      * <pre>
333      * status=200 OK
334      * result=reject
335      * message=Wrong username/password.
336      * end
337      * </pre>
338      * </li>
339      * <li>
340      * A failed request:
341      * <pre>
342      * status=400
343      * message=Server can't handle given protocol version
344      * end
345      * </pre>
346      * </li>
347      * </ul>
348      * </p>
349      *
350      * @param req DOCUMENT ME!
351      * @param res DOCUMENT ME!
352      *
353      * @exception ServletException when servlet mechanism fails
354      * @exception IOException when something fails with basic I/O, such as reading/writing to
355      * client.
356      */

357     protected void doPost(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
358         throws ServletException JavaDoc, IOException JavaDoc {
359         increaseAccess();
360
361         res.setContentType("text/plain");
362
363         ServletOutputStream JavaDoc out = res.getOutputStream();
364
365         //
366
// Keep this for logging.
367
String JavaDoc remoteAddr = req.getRemoteAddr();
368
369         //
370
// Extract information about request type and how we were called.
371
// Also suitable for logging.
372
String JavaDoc method = req.getMethod();
373         String JavaDoc path = req.getServletPath();
374         log.debug("Received request: method="+method+", path="+path);
375
376         //
377
// Will this work with content length == -1 ?? (Unknown length)
378
// Don't know, but -1 is possible only if we have a GET
379
// and we KNOW this is a POST :-)
380
Map JavaDoc params = req.getParameterMap();
381
382         try {
383             // Extract parameters from client
384
String JavaDoc username = "";
385             String JavaDoc password = "";
386             String JavaDoc version = "";
387
388             try {
389                 username = ((String JavaDoc[]) params.get(REQUEST_USERNAME))[0];
390                 password = ((String JavaDoc[]) params.get(REQUEST_PASSWORD))[0];
391                 version = ((String JavaDoc[]) params.get(REQUEST_VERSION))[0];
392             } catch (ArrayIndexOutOfBoundsException JavaDoc ignored) {
393                 // No parameters will result in "" being used from
394
// step above
395
} catch (NullPointerException JavaDoc ignoredAsWell) {
396                 // No parameters will result in "" being used from
397
// step above
398
}
399
400             //
401
// Extract and verify protocol version
402
int majorversion = 0;
403             int minorversion = 0;
404
405             // Split version on '.'
406
int dotAt = version.indexOf('.');
407
408             if (dotAt == -1) {
409                 // No separator entered, assume minor == 0
410
try {
411                     majorversion = Integer.parseInt(version);
412                 } catch (NumberFormatException JavaDoc nfe) {
413                     errorLog("doPost: Got " + nfe + " on call from " + remoteAddr +
414                         " for username '" + username +
415                         "'. Asuming version is OK. Tried to parse '" + version + "'");
416                 }
417
418                 minorversion = 0;
419             } else {
420                 try {
421                     majorversion = Integer.parseInt(version.substring(0, dotAt));
422                     minorversion = Integer.parseInt(version.substring(1 + dotAt, version.length()));
423                 } catch (NumberFormatException JavaDoc nfe) {
424                     errorLog("doPost: Got " + nfe + " on call from " + remoteAddr +
425                         " for username '" + username +
426                         "'. Asuming version is OK. Tried to parse '" + version + "'");
427                 }
428             }
429
430             //
431
// Now let's make sure we can play this tune
432
if ((majorversion == PROTOCOL_VERSION_MAJOR) &&
433                     (minorversion <= PROTOCOL_VERSION_MINOR)) {
434                 // We're in business, protocol matches
435
// This is the call to what the customer usually
436
// needs to care about.
437
// The call itself seldom needs to be changed...
438
//
439
// You should, of course, make sure that you like
440
// the given code, as it's only an example!
441
AuthResult result = authenticateUser(username, password);
442
443                 //
444
// Now build the result we'll send to the client
445
// We treat grant and rejects slightly different
446
if (result.granted()) {
447                     increaseGranted();
448                     out.println(RESPONSE_STATUS + "=" + MSG_OK);
449                     out.println(RESPONSE_RESULT + "=" + GRANT);
450                     debugLog("GRANTING request for '" + username + "'");
451
452                     // loop over all elements in resultHash, print one by one
453
Hashtable JavaDoc resultParams = result.getResult();
454                     String JavaDoc key;
455
456                     // Standard code for printing a Hash.
457
for (Enumeration JavaDoc keys = resultParams.keys(); keys.hasMoreElements();) {
458                         key = (String JavaDoc) keys.nextElement();
459                         out.println(key + "=" + ((String JavaDoc) resultParams.get(key)));
460                     }
461                 } else { // rejected.
462
increaseRejected();
463                     out.println(RESPONSE_STATUS + "=" + MSG_OK);
464                     out.println(RESPONSE_RESULT + "=" + REJECT);
465                     out.println(RESPONSE_MESSAGE + "=" + result.getReason());
466                     debugLog("REJECTING request for '" + username + "'. Reason: " +
467                         result.getReason());
468                 }
469
470                 out.println(RESPONSE_END); // The end of response token
471
} else {
472                 // protocol missmatch, reject and return
473
out.println(RESPONSE_STATUS + "=" + MSG_PROTOCOL_MISMATCH);
474                 out.println("message=Accepting at most " + PROTOCOL_VERSION_MAJOR + "." +
475                     PROTOCOL_VERSION_MINOR);
476                 errorLog("PROTOCOL MISSMATCH. Got '" + version + "', but accepts only '" +
477                     PROTOCOL_VERSION_MAJOR + "." + PROTOCOL_VERSION_MINOR + "'");
478             }
479         } catch (Exception JavaDoc e) {
480             out.println(RESPONSE_STATUS + "=" + MSG_GENERIC_ERROR + e);
481             out.println(RESPONSE_END); // The end of response token
482
errorLog("?Caught exception ", e);
483         }
484     }
485
486     /**
487      * Gets information for a user.
488      *
489      * @param username user to lookup.
490      *
491      * @return <b>null</b> (if no user found) or String[] with [0] as passwd and [1] as certificate
492      * contents.
493      */

494     protected String JavaDoc[] findUserData(String JavaDoc username) {
495         if (users == null) {
496             debugLog("findUserData: No users found. Returning null for user '" + username + "'.");
497
498             return null;
499         }
500
501         String JavaDoc[] result = (String JavaDoc[]) users.get(username.toLowerCase());
502
503         if (result != null) {
504             debugLog("findUserData: Information for user '" + username + "'found.");
505         } else {
506             debugLog("findUserData: No information for user '" + username + "'found.");
507         }
508
509         return result;
510     }
511
512     protected synchronized void increaseAccess() {
513         countAccess++;
514     }
515
516     protected synchronized void increaseGranted() {
517         countGranted++;
518     }
519
520     protected synchronized void increaseRejected() {
521         countRejected++;
522     }
523
524     /**
525      * Loads userdatabase at first access.
526      *
527      * @param config DOCUMENT ME!
528      *
529      * @exception javax.servlet.ServletException The exception description.
530      */

531     public void init(ServletConfig JavaDoc config) throws ServletException JavaDoc {
532         super.init(config);
533
534         log = Logger.getLogger(this.getClass());
535
536         debugLog((new Date JavaDoc()).toString() + " RemoteVerify.init:");
537         loadUserDB();
538     }
539
540     /**
541      * Load user DB at servlet load time, ie first access to servlet. It's ok to call this method
542      * multiple times, since it simply clears the old cached data each time it's called.
543      */

544     protected synchronized void loadUserDB() {
545         // First we clear cached users.
546
Hashtable JavaDoc oldEnUsers = users;
547         users = null;
548
549         BufferedReader JavaDoc in = null;
550         debugLog((new Date JavaDoc()).toString() + "loadUserDB: Loading from file: '" +
551             getInitParameter("dbfilename") + "'.");
552
553         InputStream JavaDoc is = getServletContext().getResourceAsStream(getInitParameter("dbfilename"));
554         in = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(is));
555
556         String JavaDoc line;
557         boolean readMore = true;
558
559         try {
560             while (readMore) {
561                 line = in.readLine();
562
563                 if (line == null) {
564                     readMore = false;
565                 } else {
566                     if (!line.startsWith(LINE_COMMENT)) {
567                         Enumeration JavaDoc lineParts = new StringTokenizer JavaDoc(line, RECORD_SEPARATOR);
568                         String JavaDoc username = (String JavaDoc) lineParts.nextElement();
569                         debugLog("loadUserDB: username=" + username);
570
571                         String JavaDoc password = (String JavaDoc) lineParts.nextElement();
572                         debugLog("loadUserDB: password=" + password);
573
574                         String JavaDoc userDataString = (String JavaDoc) lineParts.nextElement();
575                         debugLog("loadUserDB: userDataString=" + userDataString);
576
577                         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(userDataString, DNPART_DELIMITER);
578                         debugLog("loadUserDB: st=" + st);
579
580                         String JavaDoc[] userData = new String JavaDoc[2];
581                         userData[0] = password;
582                         userData[1] = userDataString;
583                         debugLog("loadUserDB: calling addUserData." + userData);
584                         addUserData(username, userData);
585                     } else {
586                         debugLog("loadUserDB: skipping comment line." + line);
587                     }
588                 }
589             }
590         } catch (IOException JavaDoc ioe) {
591             errorLog("loadUserDB: FAILED TO PARSE FILE: '" + getInitParameter("dbfilename") + "'.");
592             errorLog("loadUserDB: Got exception: ", ioe);
593             errorLog("loadUserDB: Restored previous version of DB");
594             users = oldEnUsers;
595         } finally {
596             try {
597                 in.close();
598             } catch (IOException JavaDoc ignored) {
599             }
600         }
601
602         debugLog((new Date JavaDoc()).toString() + "loadUserDB: Done.");
603     }
604
605     /**
606      * Adds information for a user in an instance to users.
607      *
608      * @param username user to lookup.
609      * @param userData String[] with [0] as passwd and [1] as certificate contents.
610      */

611     protected void addUserData(String JavaDoc username, String JavaDoc[] userData) {
612         if (users == null) {
613             debugLog("addUserData: Creating new users.");
614             users = new Hashtable JavaDoc();
615         }
616
617         debugLog("addUserData: Adding '" + username);
618
619         users.put(username.toLowerCase(), userData);
620     }
621 } // RemoteVerifyServlet
622
Popular Tags