alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (DflCache.java)

This example Java source code file (DflCache.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

authtimewithhash, bytebuffer, dflcache, ioexception, kerberostime, krb5_rv_vno, krbaperrexception, nio, not, path, security, seekablebytechannel, set, storage, string, unsupportedoperationexception, util

The DflCache.java Java example source code

/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sun.security.krb5.internal.rcache;

import java.io.*;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.security.AccessController;
import java.util.*;

import sun.security.action.GetPropertyAction;
import sun.security.krb5.internal.KerberosTime;
import sun.security.krb5.internal.Krb5;
import sun.security.krb5.internal.KrbApErrException;
import sun.security.krb5.internal.ReplayCache;


/**
 * A dfl file is used to sustores AuthTime entries when the system property
 * sun.security.krb5.rcache is set to
 *
 *    dfl(|:path/|:path/name|:name)
 *
 * The file will be path/name. If path is not given, it will be
 *
 *    System.getProperty("java.io.tmpdir")
 *
 * If name is not given, it will be
 *
 *    service_euid
 *
 * Java does not have a method to get euid, so uid is used instead. This
 * should normally to be since a Java program is seldom used as a setuid app.
 *
 * The file has a header:
 *
 *    i16 0x0501 (KRB5_RC_VNO) in network order
 *    i32 number of seconds for lifespan (in native order, same below)
 *
 * followed by cache entries concatenated, which can be encoded in
 * 2 styles:
 *
 * The traditional style is:
 *
 *    LC of client principal
 *    LC of server principal
 *    i32 cusec of Authenticator
 *    i32 ctime of Authenticator
 *
 * The new style has a hash:
 *
 *    LC of ""
 *    LC of "HASH:%s %lu:%s %lu:%s" of (hash, clientlen, client, serverlen,
 *          server) where msghash is 32 char (lower case) text mode md5sum
 *          of the ciphertext of authenticator.
 *    i32 cusec of Authenticator
 *    i32 ctime of Authenticator
 *
 * where LC of a string means
 *
 *    i32 strlen(string) + 1
 *    octets of string, with the \0x00 ending
 *
 * The old style block is always created by MIT krb5 used even if a new style
 * is available, which means there can be 2 entries for a single Authenticator.
 * Java also does this way.
 *
 * See src/lib/krb5/rcache/rc_io.c and src/lib/krb5/rcache/rc_dfl.c.
 */
public class DflCache extends ReplayCache {

    private static final int KRB5_RV_VNO = 0x501;
    private static final int EXCESSREPS = 30;   // if missed-hit>this, recreate

    private final String source;

    private static int uid;
    static {
        try {
            // Available on Solaris, Linux and Mac. Otherwise, no _euid suffix
            Class<?> clazz = Class.forName("com.sun.security.auth.module.UnixSystem");
            uid = (int)(long)(Long)
                    clazz.getMethod("getUid").invoke(clazz.newInstance());
        } catch (Exception e) {
            uid = -1;
        }
    }

    public DflCache (String source) {
        this.source = source;
    }

    private static String defaultPath() {
        return AccessController.doPrivileged(
                new GetPropertyAction("java.io.tmpdir"));
    }

    private static String defaultFile(String server) {
        // service/host@REALM -> service
        int slash = server.indexOf('/');
        if (slash == -1) {
            // A normal principal? say, dummy@REALM
            slash = server.indexOf('@');
        }
        if (slash != -1) {
            // Should not happen, but be careful
            server= server.substring(0, slash);
        }
        if (uid != -1) {
            server += "_" + uid;
        }
        return server;
    }

    private static Path getFileName(String source, String server) {
        String path, file;
        if (source.equals("dfl")) {
            path = defaultPath();
            file = defaultFile(server);
        } else if (source.startsWith("dfl:")) {
            source = source.substring(4);
            int pos = source.lastIndexOf('/');
            int pos1 = source.lastIndexOf('\\');
            if (pos1 > pos) pos = pos1;
            if (pos == -1) {
                // Only file name
                path = defaultPath();
                file = source;
            } else if (new File(source).isDirectory()) {
                // Only path
                path = source;
                file = defaultFile(server);
            } else {
                // Full pathname
                path = null;
                file = source;
            }
        } else {
            throw new IllegalArgumentException();
        }
        return new File(path, file).toPath();
    }

    @Override
    public void checkAndStore(KerberosTime currTime, AuthTimeWithHash time)
            throws KrbApErrException {
        try {
            checkAndStore0(currTime, time);
        } catch (IOException ioe) {
            KrbApErrException ke = new KrbApErrException(Krb5.KRB_ERR_GENERIC);
            ke.initCause(ioe);
            throw ke;
        }
    }

    private synchronized void checkAndStore0(KerberosTime currTime, AuthTimeWithHash time)
            throws IOException, KrbApErrException {
        Path p = getFileName(source, time.server);
        int missed = 0;
        try (Storage s = new Storage()) {
            try {
                missed = s.loadAndCheck(p, time, currTime);
            } catch (IOException ioe) {
                // Non-existing or invalid file
                Storage.create(p);
                missed = s.loadAndCheck(p, time, currTime);
            }
            s.append(time);
        }
        if (missed > EXCESSREPS) {
            Storage.expunge(p, currTime);
        }
    }


    private static class Storage implements Closeable {
        // Static methods
        @SuppressWarnings("try")
        private static void create(Path p) throws IOException {
            try (SeekableByteChannel newChan = createNoClose(p)) {
                // Do nothing, wait for close
            }
            makeMine(p);
        }

        private static void makeMine(Path p) throws IOException {
            // chmod to owner-rw only, otherwise MIT krb5 rejects
            try {
                Set<PosixFilePermission> attrs = new HashSet<>();
                attrs.add(PosixFilePermission.OWNER_READ);
                attrs.add(PosixFilePermission.OWNER_WRITE);
                Files.setPosixFilePermissions(p, attrs);
            } catch (UnsupportedOperationException uoe) {
                // No POSIX permission. That's OK.
            }
        }

        private static SeekableByteChannel createNoClose(Path p)
                throws IOException {
            SeekableByteChannel newChan = Files.newByteChannel(
                    p, StandardOpenOption.CREATE,
                        StandardOpenOption.TRUNCATE_EXISTING,
                        StandardOpenOption.WRITE);
            ByteBuffer buffer = ByteBuffer.allocate(6);
            buffer.putShort((short)KRB5_RV_VNO);
            buffer.order(ByteOrder.nativeOrder());
            buffer.putInt(KerberosTime.getDefaultSkew());
            buffer.flip();
            newChan.write(buffer);
            return newChan;
        }

        private static void expunge(Path p, KerberosTime currTime)
                throws IOException {
            Path p2 = Files.createTempFile(p.getParent(), "rcache", null);
            try (SeekableByteChannel oldChan = Files.newByteChannel(p);
                    SeekableByteChannel newChan = createNoClose(p2)) {
                long timeLimit = currTime.getSeconds() - readHeader(oldChan);
                while (true) {
                    try {
                        AuthTime at = AuthTime.readFrom(oldChan);
                        if (at.ctime > timeLimit) {
                            ByteBuffer bb = ByteBuffer.wrap(at.encode(true));
                            newChan.write(bb);
                        }
                    } catch (BufferUnderflowException e) {
                        break;
                    }
                }
            }
            makeMine(p2);
            Files.move(p2, p,
                    StandardCopyOption.REPLACE_EXISTING,
                    StandardCopyOption.ATOMIC_MOVE);
        }

        // Instance methods
        SeekableByteChannel chan;
        private int loadAndCheck(Path p, AuthTimeWithHash time,
                KerberosTime currTime)
                throws IOException, KrbApErrException {
            int missed = 0;
            if (Files.isSymbolicLink(p)) {
                throw new IOException("Symlink not accepted");
            }
            try {
                Set<PosixFilePermission> perms =
                        Files.getPosixFilePermissions(p);
                if (uid != -1 &&
                        (Integer)Files.getAttribute(p, "unix:uid") != uid) {
                    throw new IOException("Not mine");
                }
                if (perms.contains(PosixFilePermission.GROUP_READ) ||
                        perms.contains(PosixFilePermission.GROUP_WRITE) ||
                        perms.contains(PosixFilePermission.GROUP_EXECUTE) ||
                        perms.contains(PosixFilePermission.OTHERS_READ) ||
                        perms.contains(PosixFilePermission.OTHERS_WRITE) ||
                        perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
                    throw new IOException("Accessible by someone else");
                }
            } catch (UnsupportedOperationException uoe) {
                // No POSIX permissions? Ignore it.
            }
            chan = Files.newByteChannel(p, StandardOpenOption.WRITE,
                    StandardOpenOption.READ);

            long timeLimit = currTime.getSeconds() - readHeader(chan);

            long pos = 0;
            boolean seeNewButNotSame = false;
            while (true) {
                try {
                    pos = chan.position();
                    AuthTime a = AuthTime.readFrom(chan);
                    if (a instanceof AuthTimeWithHash) {
                        if (time.equals(a)) {
                            // Exact match, must be a replay
                            throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
                        } else if (time.isSameIgnoresHash(a)) {
                            // Two different authenticators in the same second.
                            // Remember it
                            seeNewButNotSame = true;
                        }
                    } else {
                        if (time.isSameIgnoresHash(a)) {
                            // Two authenticators in the same second. Considered
                            // same if we haven't seen a new style version of it
                            if (!seeNewButNotSame) {
                                throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
                            }
                        }
                    }
                    if (a.ctime < timeLimit) {
                        missed++;
                    } else {
                        missed--;
                    }
                } catch (BufferUnderflowException e) {
                    // Half-written file?
                    chan.position(pos);
                    break;
                }
            }
            return missed;
        }

        private static int readHeader(SeekableByteChannel chan)
                throws IOException {
            ByteBuffer bb = ByteBuffer.allocate(6);
            chan.read(bb);
            if (bb.getShort(0) != KRB5_RV_VNO) {
                throw new IOException("Not correct rcache version");
            }
            bb.order(ByteOrder.nativeOrder());
            return bb.getInt(2);
        }

        private void append(AuthTimeWithHash at) throws IOException {
            // Write an entry with hash, to be followed by one without it,
            // for the benefit of old implementations.
            ByteBuffer bb;
            bb = ByteBuffer.wrap(at.encode(true));
            chan.write(bb);
            bb = ByteBuffer.wrap(at.encode(false));
            chan.write(bb);
        }

        @Override
        public void close() throws IOException {
            if (chan != null) chan.close();
            chan = null;
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java DflCache.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.