2 package net.sourceforge.guacamole.net.authentication.basic;
5 * Guacamole - Clientless Remote Desktop
6 * Copyright (C) 2010 Michael Jumper
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 import java.io.IOException;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.util.Collections;
27 import java.util.HashMap;
29 import net.sourceforge.guacamole.GuacamoleException;
30 import net.sourceforge.guacamole.net.GuacamoleProperties;
31 import org.xml.sax.Attributes;
32 import org.xml.sax.SAXException;
33 import org.xml.sax.XMLReader;
34 import org.xml.sax.helpers.DefaultHandler;
35 import org.xml.sax.helpers.XMLReaderFactory;
37 public class BasicFileAuthenticationProvider implements BasicLogin.AuthenticationProvider {
39 private long mappingTime;
40 private Map<String, AuthInfo> mapping;
42 private File getUserMappingFile() throws GuacamoleException {
44 // Get user mapping filename
45 String filename = GuacamoleProperties.getProperty("basic-user-mapping");
49 return new File(filename);
53 public synchronized void init() throws GuacamoleException {
55 // Get user mapping file
56 File mapFile = getUserMappingFile();
58 throw new GuacamoleException("Missing \"basic-user-mapping\" parameter required for basic login.");
63 BasicUserMappingContentHandler contentHandler = new BasicUserMappingContentHandler();
65 XMLReader parser = XMLReaderFactory.createXMLReader();
66 parser.setContentHandler(contentHandler);
67 parser.parse(mapFile.getAbsolutePath());
69 mappingTime = mapFile.lastModified();
70 mapping = contentHandler.getUserMapping();
73 catch (IOException e) {
74 throw new GuacamoleException("Error reading basic user mapping file.", e);
76 catch (SAXException e) {
77 throw new GuacamoleException("Error parsing basic user mapping XML.", e);
83 public BasicLogin.AuthorizedConfiguration getAuthorizedConfiguration(String username, String password) throws GuacamoleException {
85 // Check mapping file mod time
86 File userMappingFile = getUserMappingFile();
87 if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified()) {
89 // If modified recently, gain exclusive access and recheck
91 if (userMappingFile.exists() && mappingTime < userMappingFile.lastModified())
92 init(); // If still not up to date, re-init
97 AuthInfo info = mapping.get(username);
98 if (info != null && info.validate(username, password))
99 return new BasicLogin.AuthorizedConfiguration(
110 public static class AuthInfo {
112 public static enum Encoding {
117 private String auth_username;
118 private String auth_password;
119 private Encoding auth_encoding;
121 private String protocol;
122 private String hostname;
124 private String password;
126 public AuthInfo(String auth_username, String auth_password, Encoding auth_encoding) {
127 this.auth_username = auth_username;
128 this.auth_password = auth_password;
129 this.auth_encoding = auth_encoding;
132 private static final char HEX_CHARS[] = {
133 '0', '1', '2', '3', '4', '5', '6', '7',
134 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
137 public static String getHexString(byte[] bytes) {
142 StringBuilder hex = new StringBuilder(2 * bytes.length);
143 for (byte b : bytes) {
144 hex.append(HEX_CHARS[(b & 0xF0) >> 4])
145 .append(HEX_CHARS[(b & 0x0F) ]);
148 return hex.toString();
153 public boolean validate(String username, String password) {
155 // If username matches
156 if (username != null && password != null && username.equals(auth_username)) {
158 switch (auth_encoding) {
163 return password.equals(auth_password);
167 // Compare hashed password
169 MessageDigest digest = MessageDigest.getInstance("MD5");
170 String hashedPassword = getHexString(digest.digest(password.getBytes()));
171 return hashedPassword.equals(auth_password.toUpperCase());
173 catch (NoSuchAlgorithmException e) {
174 throw new UnsupportedOperationException("Unexpected lack of MD5 support.", e);
185 public String getHostname() {
189 public String getPassword() {
193 public int getPort() {
197 public String getProtocol() {
204 private static class BasicUserMappingContentHandler extends DefaultHandler {
206 private Map<String, AuthInfo> authMapping = new HashMap<String, AuthInfo>();
208 public Map<String, AuthInfo> getUserMapping() {
209 return Collections.unmodifiableMap(authMapping);
212 private AuthInfo current;
214 private enum AUTH_INFO_STATE {
221 private AUTH_INFO_STATE infoState;
224 public void endElement(String uri, String localName, String qName) throws SAXException {
226 if (localName.equals("authorize")) {
228 // Finalize mapping for this user
230 current.auth_username,
241 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
243 if (localName.equals("authorize")) {
245 AuthInfo.Encoding encoding;
246 String encodingString = attributes.getValue("encoding");
247 if (encodingString == null)
248 encoding = AuthInfo.Encoding.PLAIN_TEXT;
249 else if (encodingString.equals("plain"))
250 encoding = AuthInfo.Encoding.PLAIN_TEXT;
251 else if (encodingString.equals("md5"))
252 encoding = AuthInfo.Encoding.MD5;
254 throw new SAXException("Invalid encoding type");
257 current = new AuthInfo(
258 attributes.getValue("username"),
259 attributes.getValue("password"),
267 else if (localName.equals("protocol"))
268 infoState = AUTH_INFO_STATE.PROTOCOL;
270 else if (localName.equals("hostname"))
271 infoState = AUTH_INFO_STATE.HOSTNAME;
273 else if (localName.equals("port"))
274 infoState = AUTH_INFO_STATE.PORT;
276 else if (localName.equals("password"))
277 infoState = AUTH_INFO_STATE.PASSWORD;
285 public void characters(char[] ch, int start, int length) throws SAXException {
287 String str = new String(ch, start, length);
289 if (infoState == null)
295 current.protocol = str;
299 current.hostname = str;
303 current.port = Integer.parseInt(str);
307 current.password = str;