| /* |
| * 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) ); |
| } |
| } |
| } |