blob: 507feadb6a65de50a7fc33abb0e77ac3b1434608 [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
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 com.sun.enterprise.util.Utility;
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
}
}
@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;
ProcessBuilder pb = new ProcessBuilder("chmod", "0600", store.getAbsolutePath());
pb.start();
}
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 char[] password = Utility.convertByteArrayToCharArray(decoder.decodeBuffer(encp), null);
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 char[] password = login.getPassword();
final String encp = encoder.encode(Utility.convertCharArrayToByteArray(password,null));
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) );
}
}
}