/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.appserv.management.client.prefs;
import com.sun.enterprise.security.store.AsadminSecurityUtil;
import com.sun.enterprise.universal.GFBase64Decoder;
import com.sun.enterprise.universal.GFBase64Encoder;
import org.glassfish.security.common.FileProtectionUtility;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/** A {@link LoginInfoStore} that reads the information from the default file ".gfclient/pass"
* and stores it as a map in the memory. It is not guaranteed that the concurrent
* modifications will yield consistent results. This class is <i> not </i> thread safe. The
* serial access has to be ensured by the callers.
* @since Appserver 9.0
*/
public class MemoryHashLoginInfoStore implements LoginInfoStore {
public static final String DEFAULT_STORE_NAME = "pass";
private static final GFBase64Encoder encoder = new GFBase64Encoder();
private static final GFBase64Decoder decoder = new GFBase64Decoder();
private Map<HostPortKey, LoginInfo> state;
private final File store;
/**
* Creates a new instance of MemoryHashLoginInfoStore. A side effect of calling
* this constructor is that if the default store does not exist, it will be created.
* This does not pose any harm or surprises.
*/
public MemoryHashLoginInfoStore() throws StoreException {
BufferedReader br = null;
BufferedWriter bw = null;
try {
final File dir = AsadminSecurityUtil.getDefaultClientDir();
store = new File(dir, DEFAULT_STORE_NAME);
if (store.createNewFile()) {
bw = new BufferedWriter(new FileWriter(store));
FileMapTransform.writePreamble(bw);
state = new HashMap<HostPortKey, LoginInfo> ();
}
else {
br = new BufferedReader(new FileReader(store));
state = FileMapTransform.readAll(br);
}
}
catch(final Exception e) {
throw new StoreException(e);
}
finally {
try {
if (br != null)
br.close();
}
catch(final Exception ee) {} //ignore
try {
if (bw != null)
bw.close();
}
catch(final Exception ee) {} //ignore
}
protect();
}
@Override
public void store(final LoginInfo login) throws StoreException {
this.store(login, false);
}
@Override
public void store(final LoginInfo login, boolean overwrite) throws StoreException {
if (login == null)
throw new IllegalArgumentException("null_arg");
final String host = login.getHost();
final int port = login.getPort();
if (!overwrite && this.exists(host, port)) {
throw new StoreException("Login exists for host: " + host + " port: " + port);
}
final HostPortKey key = new HostPortKey(host, port);
final LoginInfo old = state.get(key);
state.put(key, login);
//System.out.println("committing: " + login);
commit(key, old);
protect();
}
@Override
public void remove(final String host, final int port) {
final HostPortKey key = new HostPortKey(host, port);
final LoginInfo gone = state.remove(key);
commit(key, gone);
}
@Override
public LoginInfo read(String host, int port) {
final HostPortKey key = new HostPortKey(host, port);
final LoginInfo login = state.get(key); //no need to access disk
return ( login );
}
@Override
public boolean exists(String host, int port) {
final HostPortKey key = new HostPortKey(host, port);
final boolean exists = state.containsKey(key); //no need to access disk
return ( exists );
}
@Override
public int size() {
return ( state.size() ); // no need to access disk
}
@Override
public Collection<LoginInfo> list() {
final Collection<LoginInfo> logins = state.values(); // no need to access disk
return (Collections.unmodifiableCollection(logins) );
}
@Override
public String getName() {
return ( store.getAbsoluteFile().getAbsolutePath() );
}
///// PRIVATE METHODS /////
private void commit(final HostPortKey key, LoginInfo old) {
BufferedWriter writer = null;
try {
//System.out.println("before commit");
writer = new BufferedWriter(new FileWriter(store));
FileMapTransform.writeAll(state.values(), writer);
}
catch(final Exception e) {
state.put(key, old); //try to roll back, first memory
try { // then disk, if the old value is not null
if (old != null) {
writer = new BufferedWriter(new FileWriter(store));
FileMapTransform.writeAll(state.values(), writer);
}
}
catch(final Exception ae) {
throw new RuntimeException("catastrophe, can't write it to file");
}//ignore, can't do much
}
finally {
try {
writer.close();
} catch(final Exception ee) {} //ignore
}
}
private void protect()
{
/*
note: if this is Windows we still try 'chmod' -- they may have MKS or
some other UNIXy package for Windows.
cacls is too dangerous to use because it requires a "Y" to be written to
stdin of the cacls process. If cacls doesn't exist or if they are using
a non-NTFS file system we would hang here forever.
*/
try
{
if(store == null || !store.exists())
return;
FileProtectionUtility.chmod0600(store);
}
catch(Exception e)
{
// we tried...
}
}
private static class FileMapTransform {
private FileMapTransform() {} //disallow
static Map<HostPortKey, LoginInfo> readAll(final BufferedReader reader) throws IOException, URISyntaxException {
String line;
final Map<HostPortKey, LoginInfo> map = new HashMap<HostPortKey, LoginInfo> ();
while ((line = reader.readLine()) != null) {
if (line.startsWith("#"))
continue; //ignore comments
final int si = line.indexOf(' '); //index of space
if (si == -1)
throw new IOException("Error: invalid record: " + line);
final URI uri = new URI(line.substring(0, si));
final String encp = line.substring(si+1, line.length());
final HostPortKey key = uri2Key(uri);
final LoginInfo value = line2LoginInfo(uri, encp);
map.put(key, value);
}
return ( map );
}
static void writeAll(final Collection<LoginInfo> logins, final BufferedWriter writer) throws IOException, URISyntaxException {
writePreamble(writer);
//write out sorted, because not more than 100 logins are expected to be there
final List<LoginInfo> list = new ArrayList<LoginInfo>(logins);
Collections.sort(list);
final Iterator<LoginInfo> it = list.iterator();
while (it.hasNext()) {
final LoginInfo login = it.next();
//System.out.println("wrote: " + login);
writeOne(login, writer);
}
}
private static void writeOne(final LoginInfo login, final BufferedWriter writer) throws IOException, URISyntaxException {
writer.write(login2Line(login));
writer.newLine();
}
static HostPortKey uri2Key(final URI uri) {
final String host = uri.getHost();
final int port = uri.getPort();
final HostPortKey key = new HostPortKey(host, port);
return ( key );
}
static LoginInfo line2LoginInfo(final URI uri, final String encp) throws IOException {
final String host = uri.getHost();
final int port = uri.getPort();
final String user = uri.getUserInfo();
final String password = new String(decoder.decodeBuffer(encp));
return ( new LoginInfo(host, port, user, password) );
}
static String login2Line(final LoginInfo login) throws IOException, URISyntaxException {
final String scheme = "asadmin";
final String host = login.getHost();
final int port = login.getPort();
final String user = login.getUser();
final URI uri = new URI(scheme, user, host, port, null, null, null);
final String password = login.getPassword();
final String encp = encoder.encode(password.getBytes());
final String line = uri.toString() + ' ' + encp;
return ( line );
}
static void writePreamble(final BufferedWriter bw) throws IOException {
final String preamble = "# Do not edit this file by hand. Use login interface instead.";
bw.write(preamble);
bw.newLine();
}
}
private static class HostPortKey {
private final String host;
private final int port;
HostPortKey(final String host, final int port) {
this.host = host;
this.port = port;
}
@Override
public boolean equals(final Object other) {
boolean same = false;
if (other instanceof HostPortKey) {
final HostPortKey that = (HostPortKey)other;
same = this.host.equals(that.host) && this.port == that.port;
}
return ( same );
}
@Override
public int hashCode() {
return ( (int) (53 * host.hashCode() + 31 * port) );
}
}
}