/* =============================================================================== * * Part of the InfoGlue Content Management Platform (www.infoglue.org) * * =============================================================================== * * Copyright (C) * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2, as published by the * Free Software Foundation. See the file LICENSE.html for more information. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple * Place, Suite 330 / Boston, MA 02111-1307 / USA. * * =============================================================================== */ package org.infoglue.deliver.taglib.common; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.util.Random; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import javax.servlet.jsp.JspException; import org.apache.axis.encoding.Base64; import org.apache.log4j.Logger; import org.infoglue.cms.util.CmsPropertyHandler; /** * A Tag used for rendering distorted images with random text. * @author Tommy Berglund <a href="mailto:tommy.berglund@hotmail.com">tommy.berglund@modul1.se</a> */ public class GapchaTag extends TextRenderTag { private final static Logger logger = Logger.getLogger(GapchaTag.class.getName()); /** * */ private static final long serialVersionUID = 1L; private String allowedCharacters = "abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private String textVariableName = "CAPTHCA_TEXT"; private int numberOfCharacters = 5; private static int requestsNO = 0; /** If set this value will be interpreted as a variable name to store the Captcha instance ticket. * If this value is set the tag will not store the Captcha ticket in the sessions. That means that * the tag caller has to handle the ticket and pass it with the form submit. */ private String ticket; private static final String DEFAULT_PASSWORD = "TOPSECRETPASSWORDTHATNOONEKNOWS"; private String password ; private static final byte[] SALT = { (byte) 0xde, (byte) 0x73, (byte) 0x10, (byte) 0xa2, (byte) 0xde, (byte) 0x73, (byte) 0x10, (byte) 0xa2, }; public GapchaTag() { super(); } public int doEndTag() throws JspException { if(requestsNO > 50) { cleanOldFiles(); requestsNO = 0; } // create the random string char[] randomCharacters = createRandomCharacters(); if (ticket == null) { logger.info("Generating Gaptcha with session stored verification"); String sessionVariableName = textVariableName + "_" + System.currentTimeMillis(); pageContext.getSession().setAttribute( sessionVariableName, new String(randomCharacters) ); pageContext.setAttribute(textVariableName, sessionVariableName); } else { try { logger.info("Generating Gaptcha with encoded ticket"); pageContext.setAttribute(ticket, encodeTicket(new String(randomCharacters))); } catch (Exception ex) { logger.error("Error generating encrypted ticket for Gapcha. Message: " + ex.getMessage()); logger.warn("Error generating encrypted ticket for Gapcha.", ex); throw new JspException("Error generating captcha"); } } // set the random string in the session // without spacing it is really hard to read the text String randomText = spaceCharacters(randomCharacters); try { result = this.getController().getRenderedTextUrl( randomText, renderAttributes, true ); } catch (Exception e) { e.printStackTrace(); } this.produceResult( result ); requestsNO++; return EVAL_PAGE; } private String encodeTicket(String characters) throws GeneralSecurityException, UnsupportedEncodingException { if (password == null) { password = DEFAULT_PASSWORD; } SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray())); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); return Base64.encode(pbeCipher.doFinal(characters.getBytes("UTF-8"))); } /* default */ static String decodeTicket(String ticket, String password) throws GeneralSecurityException, IOException { if (password == null) { password = DEFAULT_PASSWORD; } SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray())); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); return new String(pbeCipher.doFinal(Base64.decode(ticket)), "UTF-8"); } public static void cleanOldFiles() { int i = 0; String filePath = CmsPropertyHandler.getDigitalAssetPath0(); while ( filePath != null ) { logger.info("Cleaning files..."); File folder = new File(filePath); File[] files = folder.listFiles(); logger.info("files:" + files.length); for(int j=0; j<files.length; j++) { File file = files[j]; if(file.getName().startsWith("igcaptcha")) { logger.info("file.getName():" + file.getName() + " - " + (System.currentTimeMillis() - file.lastModified())); if(System.currentTimeMillis() - file.lastModified() > 60000) { logger.info("Deleting:" + file.getName()); file.delete(); } } } i++; filePath = CmsPropertyHandler.getProperty( "digitalAssetPath." + i ); } } /** * Sets the number of characters in the random string. Value must be greater * than 0, otherwise default value (5) is used. * @param numberOfCharacters the number of characters */ public void setNumberOfCharacters( int numberOfCharacters ) { if( numberOfCharacters > 0 ) { this.numberOfCharacters = numberOfCharacters; } } /** * Sets the variable name by which to store the captcha text in. * @param numberOfCharacters the number of characters */ public void setTextVariableName( String textVariableName ) { this.textVariableName = textVariableName; } /** * Creates a char[] of random characters and numbers a-z,A-Z,0-9. * The number of characters can be set by an attribute. Default is 5. * @return a char[] of random characters and numbers */ private char[] createRandomCharacters() { Random r = new Random(); char[] buf = new char[numberOfCharacters]; for (int i = 0; i < buf.length; i++) { buf[i] = allowedCharacters.charAt(r.nextInt(allowedCharacters.length())); } return buf; } /** * Spaces characters for easier reading * @param characters the characters to space * @return a <code>String</code> of spaced characters */ private String spaceCharacters( char[] characters ) { StringBuffer sb = new StringBuffer(characters.length*2); for( int i = 0; i <characters.length; i++ ) { sb.append( characters[i] ); sb.append( " " ); } return sb.toString(); } public void setTwirlAngle(String twirlAspect) throws JspException { this.setAttribute("twirlAspect", ((Float)evaluate("gapcha", "twirlAspect", twirlAspect, Float.class)).floatValue()); } public void setMarbleXScale(String marbleXScale) throws JspException { this.setAttribute("marbleXScale", ((Float)evaluate("gapcha", "marbleXScale", marbleXScale, Float.class)).floatValue()); } public void setMarbleYScale(String marbleYScale) throws JspException { this.setAttribute("marbleYScale", ((Float)evaluate("gapcha", "marbleYScale", marbleYScale, Float.class)).floatValue()); } public void setMarbleTurbulence(String marbleTurbulence) throws JspException { this.setAttribute("marbleTurbulence", ((Float)evaluate("gapcha", "marbleTurbulence", marbleTurbulence, Float.class)).floatValue()); } public void setMarbleAmount(String marbleAmount) throws JspException { this.setAttribute("marbleAmount", ((Float)evaluate("gapcha", "marbleAmount", marbleAmount, Float.class)).floatValue()); } public void setAllowedCharacters(String allowedCharacters) throws JspException { this.allowedCharacters = evaluateString("gapcha", "allowedCharacters", allowedCharacters); } public void setTwirlAngle(Object twirlAspect) throws JspException { this.setAttribute("twirlAspect", (Float)twirlAspect); } public void setMarbleXScale(Object marbleXScale) throws JspException { this.setAttribute("marbleXScale", (Float)marbleXScale); } public void setMarbleYScale(Object marbleYScale) throws JspException { this.setAttribute("marbleYScale", (Float)marbleYScale); } public void setMarbleTurbulence(Object marbleTurbulence) throws JspException { this.setAttribute("marbleTurbulence", (Float)marbleTurbulence); } public void setMarbleAmount(Object marbleAmount) throws JspException { this.setAttribute("marbleAmount", (Float)marbleAmount); } public void setTicket(String ticket) { this.ticket = ticket; } public void setPassword(String password) throws JspException { this.password = evaluateString("gapcha", "password", password); } public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException, IOException { System.out.println("Begin"); GapchaTag tag = new GapchaTag(); String enc1 = tag.encodeTicket("apa"); System.out.println("Test 1: apa = " + GapchaTag.decodeTicket(enc1, null) + " || " + enc1); String enc2 = tag.encodeTicket("bepa123"); System.out.println("Test 2: bepa123 = " + GapchaTag.decodeTicket(enc2, null) + " || " + enc2); System.out.println("Test 3: ? = " + GapchaTag.decodeTicket("Bfck1bYW8r4=", null)); System.out.println("End"); } }