001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.system.configuration;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileReader;
022    import java.io.FileWriter;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.LineNumberReader;
026    import java.io.OutputStream;
027    import java.security.MessageDigest;
028    import java.security.NoSuchAlgorithmException;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    
033    /**
034     * Utility methods for dealing with checksums (hashes) of files in the
035     * configuration store.
036     *
037     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
038     */
039    public class ConfigurationStoreUtil {
040        private static final Log log = LogFactory.getLog(ConfigurationStoreUtil.class);
041    
042        public static void writeChecksumFor(File file) throws IOException {
043            // check if the sum already exists
044            File sumFile = new File(file.getParentFile(), file.getName() + ".sha1");
045            if (sumFile.exists()) {
046                throw new IOException("Sum file already exists");
047            }
048    
049            // calculate the checksum
050            String actualChecksum;
051            try {
052                actualChecksum = calculateChecksum(file, "SHA-1");
053            } catch (NoSuchAlgorithmException e) {
054                throw (IOException)new IOException("SHA-1 algorithm not available").initCause(e);
055            }
056    
057            // write it
058            FileWriter writer = new FileWriter(sumFile);
059            try {
060                writer.write(actualChecksum);
061            } finally {
062                try {
063                    writer.close();
064                } catch (IOException ignored) {
065                }
066            }
067        }
068    
069        public static boolean verifyChecksum(File file) {
070            String expectedChecksum = getExpectedChecksum(file);
071            if (expectedChecksum == null) {
072                // log message already printed
073                return false;
074            }
075    
076            String actualChecksum = getActualChecksum(file);
077            if (actualChecksum == null) {
078                // log message already printed
079                return false;
080            }
081    
082    
083            if (!actualChecksum.equals(expectedChecksum)) {
084                log.warn("Configuration file was modified: " + file.getAbsolutePath());
085                return false;
086            }
087    
088            return true;
089        }
090    
091        public static String getExpectedChecksum(File file) {
092            File sumFile = new File(file.getParentFile(), file.getName() + ".sha1");
093            if (!sumFile.exists()) {
094                log.warn("Checksum file not found: " + sumFile.getAbsolutePath());
095                return null;
096            }
097            if (!sumFile.canRead()) {
098                log.warn("Checksum file is not readable: " + sumFile.getAbsolutePath());
099                return null;
100            }
101            LineNumberReader lineNumberReader = null;
102            try {
103                lineNumberReader = new LineNumberReader(new FileReader(sumFile));
104                String expectedChecksum = lineNumberReader.readLine();
105                if (expectedChecksum == null) {
106                    log.error("Checksum file was empty: " + sumFile.getAbsolutePath());
107                    return null;
108                }
109                return expectedChecksum.trim();
110            } catch (IOException e) {
111                log.error("Unable to read checksum file: " + sumFile.getAbsolutePath(), e);
112            } finally {
113                if (lineNumberReader != null) {
114                    try {
115                        lineNumberReader.close();
116                    } catch (IOException ignored) {
117                    }
118                }
119    
120            }
121            return null;
122        }
123    
124        public static String getActualChecksum(File file) {
125            return getActualChecksum(file, "SHA-1");
126        }
127        public static String getActualChecksum(File file, String algorithm) {
128            try {
129                return calculateChecksum(file, algorithm);
130            } catch (Exception e) {
131                log.error("Unable to calculate checksum for configuration file: " + file.getAbsolutePath(), e);
132            }
133            return null;
134        }
135    
136        private static String calculateChecksum(File file, String algorithm) throws NoSuchAlgorithmException, IOException {
137    
138            InputStream stream = null;
139            try {
140                stream = new FileInputStream(file);
141                
142                MessageDigest digester = MessageDigest.getInstance(algorithm);
143                digester.reset();
144    
145                byte buf[] = new byte[4096];
146                int len = 0;
147    
148                while ((len = stream.read(buf, 0, 1024)) != -1) {
149                    digester.update(buf, 0, len);
150                }
151    
152                String actualChecksum = encode(digester.digest());
153                return actualChecksum;
154            } finally {
155                try {
156                    if (stream != null)
157                        stream.close();
158                } catch (IOException ignored) {
159                }
160            }
161        }
162    
163        private static String encode(byte[] binaryData) {
164            if (binaryData.length != 16 && binaryData.length != 20) {
165                int bitLength = binaryData.length * 8;
166                throw new IllegalArgumentException("Unrecognised length for binary data: " + bitLength + " bits");
167            }
168    
169            String retValue = "";
170    
171            for (int i = 0; i < binaryData.length; i++) {
172                String t = Integer.toHexString(binaryData[i] & 0xff);
173    
174                if (t.length() == 1) {
175                    retValue += ("0" + t);
176                } else {
177                    retValue += t;
178                }
179            }
180    
181            return retValue.trim();
182        }
183    
184        public static class ChecksumOutputStream extends OutputStream {
185            private final OutputStream out;
186            private MessageDigest digester;
187    
188            public ChecksumOutputStream(OutputStream out) throws IOException {
189                this.out = out;
190                try {
191                    digester = MessageDigest.getInstance("SHA-1");
192                    digester.reset();
193                } catch (NoSuchAlgorithmException e) {
194                    throw (IOException)new IOException("SHA-1 algorithm not available").initCause(e);
195                }
196            }
197    
198            public String getChecksum() {
199                String actualChecksum = encode(digester.digest());
200                return actualChecksum;
201            }
202    
203            public void write(int b) throws IOException {
204                digester.update((byte) b);
205                out.write(b);
206            }
207    
208            public void write(byte[] b) throws IOException {
209                digester.update(b);
210                out.write(b);
211            }
212    
213            public void write(byte[] b, int off, int len) throws IOException {
214                digester.update(b, off, len);
215                out.write(b, off, len);
216            }
217    
218            public void flush() throws IOException {
219                out.flush();
220            }
221    
222            public void close() throws IOException {
223                out.close();
224            }
225        }
226    }