1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * ===========================================================================
28 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
29 *
30 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
31 * ===========================================================================
32 *
33 */
34package sun.security.krb5.internal.ccache;
35
36import sun.security.krb5.*;
37import sun.security.krb5.internal.*;
38import java.util.StringTokenizer;
39import java.util.Vector;
40import java.io.IOException;
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileOutputStream;
44import java.io.BufferedReader;
45import java.io.InputStreamReader;
46
47/**
48 * CredentialsCache stores credentials(tickets, session keys, etc) in a
49 * semi-permanent store
50 * for later use by different program.
51 *
52 * @author Yanni Zhang
53 * @author Ram Marti
54 */
55
56public class FileCredentialsCache extends CredentialsCache
57    implements FileCCacheConstants {
58    public int version;
59    public Tag tag; // optional
60    public PrincipalName primaryPrincipal;
61    private Vector<Credentials> credentialsList;
62    private static String dir;
63    private static boolean DEBUG = Krb5.DEBUG;
64
65    public static synchronized FileCredentialsCache acquireInstance(
66                PrincipalName principal, String cache) {
67        try {
68            FileCredentialsCache fcc = new FileCredentialsCache();
69            if (cache == null) {
70                cacheName = FileCredentialsCache.getDefaultCacheName();
71            } else {
72                cacheName = FileCredentialsCache.checkValidation(cache);
73            }
74            if ((cacheName == null) || !(new File(cacheName)).exists()) {
75                // invalid cache name or the file doesn't exist
76                return null;
77            }
78            if (principal != null) {
79                fcc.primaryPrincipal = principal;
80            }
81            fcc.load(cacheName);
82            return fcc;
83        } catch (IOException e) {
84            // we don't handle it now, instead we return a null at the end.
85            if (DEBUG) {
86                e.printStackTrace();
87            }
88        } catch (KrbException e) {
89            // we don't handle it now, instead we return a null at the end.
90            if (DEBUG) {
91                e.printStackTrace();
92            }
93        }
94        return null;
95    }
96
97    public static FileCredentialsCache acquireInstance() {
98        return acquireInstance(null, null);
99    }
100
101    static synchronized FileCredentialsCache New(PrincipalName principal,
102                                                String name) {
103        try {
104            FileCredentialsCache fcc = new FileCredentialsCache();
105            cacheName = FileCredentialsCache.checkValidation(name);
106            if (cacheName == null) {
107                // invalid cache name or the file doesn't exist
108                return null;
109            }
110            fcc.init(principal, cacheName);
111            return fcc;
112        }
113        catch (IOException e) {
114        }
115        catch (KrbException e) {
116        }
117        return null;
118    }
119
120    static synchronized FileCredentialsCache New(PrincipalName principal) {
121        try {
122            FileCredentialsCache fcc = new FileCredentialsCache();
123            cacheName = FileCredentialsCache.getDefaultCacheName();
124            fcc.init(principal, cacheName);
125            return fcc;
126        }
127        catch (IOException e) {
128            if (DEBUG) {
129                e.printStackTrace();
130            }
131        } catch (KrbException e) {
132            if (DEBUG) {
133                e.printStackTrace();
134            }
135
136        }
137        return null;
138    }
139
140    private FileCredentialsCache() {
141    }
142
143    boolean exists(String cache) {
144        File file = new File(cache);
145        if (file.exists()) {
146            return true;
147        } else return false;
148    }
149
150    synchronized void init(PrincipalName principal, String name)
151        throws IOException, KrbException {
152        primaryPrincipal = principal;
153        try (FileOutputStream fos = new FileOutputStream(name);
154             CCacheOutputStream cos = new CCacheOutputStream(fos)) {
155            version = KRB5_FCC_FVNO_3;
156            cos.writeHeader(primaryPrincipal, version);
157        }
158        load(name);
159    }
160
161    synchronized void load(String name) throws IOException, KrbException {
162        PrincipalName p;
163        try (FileInputStream fis = new FileInputStream(name);
164             CCacheInputStream cis = new CCacheInputStream(fis)) {
165            version = cis.readVersion();
166            if (version == KRB5_FCC_FVNO_4) {
167                tag = cis.readTag();
168            } else {
169                tag = null;
170                if (version == KRB5_FCC_FVNO_1 || version == KRB5_FCC_FVNO_2) {
171                    cis.setNativeByteOrder();
172                }
173            }
174            p = cis.readPrincipal(version);
175
176            if (primaryPrincipal != null) {
177                if (!(primaryPrincipal.match(p))) {
178                    throw new IOException("Primary principals don't match.");
179                }
180            } else
181                primaryPrincipal = p;
182            credentialsList = new Vector<Credentials>();
183            while (cis.available() > 0) {
184                Credentials cred = cis.readCred(version);
185                if (cred != null) {
186                    credentialsList.addElement(cred);
187                }
188            }
189        }
190    }
191
192
193    /**
194     * Updates the credentials list. If the specified credentials for the
195     * service is new, add it to the list. If there is an entry in the list,
196     * replace the old credentials with the new one.
197     * @param c the credentials.
198     */
199
200    public synchronized void update(Credentials c) {
201        if (credentialsList != null) {
202            if (credentialsList.isEmpty()) {
203                credentialsList.addElement(c);
204            } else {
205                Credentials tmp = null;
206                boolean matched = false;
207
208                for (int i = 0; i < credentialsList.size(); i++) {
209                    tmp = credentialsList.elementAt(i);
210                    if (match(c.sname.getNameStrings(),
211                              tmp.sname.getNameStrings()) &&
212                        ((c.sname.getRealmString()).equalsIgnoreCase(
213                                     tmp.sname.getRealmString()))) {
214                        matched = true;
215                        if (c.endtime.getTime() >= tmp.endtime.getTime()) {
216                            if (DEBUG) {
217                                System.out.println(" >>> FileCredentialsCache "
218                                         +  "Ticket matched, overwrite "
219                                         +  "the old one.");
220                            }
221                            credentialsList.removeElementAt(i);
222                            credentialsList.addElement(c);
223                        }
224                    }
225                }
226                if (matched == false) {
227                    if (DEBUG) {
228                        System.out.println(" >>> FileCredentialsCache Ticket "
229                                        +   "not exactly matched, "
230                                        +   "add new one into cache.");
231                    }
232
233                    credentialsList.addElement(c);
234                }
235            }
236        }
237    }
238
239    public synchronized PrincipalName getPrimaryPrincipal() {
240        return primaryPrincipal;
241    }
242
243
244    /**
245     * Saves the credentials cache file to the disk.
246     */
247    public synchronized void save() throws IOException, Asn1Exception {
248        try (FileOutputStream fos = new FileOutputStream(cacheName);
249             CCacheOutputStream cos = new CCacheOutputStream(fos)) {
250            cos.writeHeader(primaryPrincipal, version);
251            Credentials[] tmp = null;
252            if ((tmp = getCredsList()) != null) {
253                for (int i = 0; i < tmp.length; i++) {
254                    cos.addCreds(tmp[i]);
255                }
256            }
257        }
258    }
259
260    boolean match(String[] s1, String[] s2) {
261        if (s1.length != s2.length) {
262            return false;
263        } else {
264            for (int i = 0; i < s1.length; i++) {
265                if (!(s1[i].equalsIgnoreCase(s2[i]))) {
266                    return false;
267                }
268            }
269        }
270        return true;
271    }
272
273    /**
274     * Returns the list of credentials entries in the cache file.
275     */
276    public synchronized Credentials[] getCredsList() {
277        if ((credentialsList == null) || (credentialsList.isEmpty())) {
278            return null;
279        } else {
280            Credentials[] tmp = new Credentials[credentialsList.size()];
281            for (int i = 0; i < credentialsList.size(); i++) {
282                tmp[i] = credentialsList.elementAt(i);
283            }
284            return tmp;
285        }
286
287    }
288
289    public Credentials getCreds(LoginOptions options, PrincipalName sname) {
290        if (options == null) {
291            return getCreds(sname);
292        } else {
293            Credentials[] list = getCredsList();
294            if (list == null) {
295                return null;
296            } else {
297                for (int i = 0; i < list.length; i++) {
298                    if (sname.match(list[i].sname)) {
299                        if (list[i].flags.match(options)) {
300                            return list[i];
301                        }
302                    }
303                }
304            }
305            return null;
306        }
307    }
308
309
310    /**
311     * Gets a credentials for a specified service.
312     * @param sname service principal name.
313     */
314    public Credentials getCreds(PrincipalName sname) {
315        Credentials[] list = getCredsList();
316        if (list == null) {
317            return null;
318        } else {
319            for (int i = 0; i < list.length; i++) {
320                if (sname.match(list[i].sname)) {
321                    return list[i];
322                }
323            }
324        }
325        return null;
326    }
327
328    public Credentials getDefaultCreds() {
329        Credentials[] list = getCredsList();
330        if (list == null) {
331            return null;
332        } else {
333            for (int i = list.length-1; i >= 0; i--) {
334                if (list[i].sname.toString().startsWith("krbtgt")) {
335                    String[] nameStrings = list[i].sname.getNameStrings();
336                    // find the TGT for the current realm krbtgt/realm@realm
337                    if (nameStrings[1].equals(list[i].sname.getRealm().toString())) {
338                       return list[i];
339                    }
340                }
341            }
342        }
343        return null;
344    }
345
346    /*
347     * Returns path name of the credentials cache file.
348     * The path name is searched in the following order:
349     *
350     * 1. KRB5CCNAME (bare file name without FILE:)
351     * 2. /tmp/krb5cc_<uid> on unix systems
352     * 3. <user.home>/krb5cc_<user.name>
353     * 4. <user.home>/krb5cc (if can't get <user.name>)
354     */
355
356    public static String getDefaultCacheName() {
357
358        String stdCacheNameComponent = "krb5cc";
359        String name;
360
361        // The env var can start with TYPE:, we only support FILE: here.
362        // http://docs.oracle.com/cd/E19082-01/819-2252/6n4i8rtr3/index.html
363        name = java.security.AccessController.doPrivileged(
364                new java.security.PrivilegedAction<String>() {
365            @Override
366            public String run() {
367                String cache = System.getenv("KRB5CCNAME");
368                if (cache != null &&
369                        (cache.length() >= 5) &&
370                        cache.regionMatches(true, 0, "FILE:", 0, 5)) {
371                    cache = cache.substring(5);
372                }
373                return cache;
374            }
375        });
376        if (name != null) {
377            if (DEBUG) {
378                System.out.println(">>>KinitOptions cache name is " + name);
379            }
380            return name;
381        }
382
383        // get cache name from system.property
384        String osname =
385            java.security.AccessController.doPrivileged(
386                        new sun.security.action.GetPropertyAction("os.name"));
387
388        /*
389         * For Unix platforms we use the default cache name to be
390         * /tmp/krb5cc_uid ; for all other platforms  we use
391         * {user_home}/krb5cc_{user_name}
392         * Please note that for Windows we will use LSA to get
393         * the TGT from the default cache even before we come here;
394         * however when we create cache we will create a cache under
395         * {user_home}/krb5cc_{user_name} for non-Unix platforms including
396         * Windows.
397         */
398
399        if (osname != null && !osname.startsWith("Windows")) {
400            long uid = jdk.internal.misc.VM.getuid();
401            if (uid != -1) {
402                name = File.separator + "tmp" +
403                        File.separator + stdCacheNameComponent + "_" + uid;
404                if (DEBUG) {
405                    System.out.println(">>>KinitOptions cache name is " +
406                            name);
407                }
408                return name;
409            } else {
410                if (DEBUG) {
411                    System.out.println("Error in obtaining uid " +
412                                        "for Unix platforms " +
413                                        "Using user's home directory");
414                }
415            }
416        }
417
418        // we did not get the uid;
419
420        String user_name =
421            java.security.AccessController.doPrivileged(
422                        new sun.security.action.GetPropertyAction("user.name"));
423
424        String user_home =
425            java.security.AccessController.doPrivileged(
426                        new sun.security.action.GetPropertyAction("user.home"));
427
428        if (user_home == null) {
429            user_home =
430                java.security.AccessController.doPrivileged(
431                        new sun.security.action.GetPropertyAction("user.dir"));
432        }
433
434        if (user_name != null) {
435            name = user_home + File.separator  +
436                stdCacheNameComponent + "_" + user_name;
437        } else {
438            name = user_home + File.separator + stdCacheNameComponent;
439        }
440
441        if (DEBUG) {
442            System.out.println(">>>KinitOptions cache name is " + name);
443        }
444
445        return name;
446    }
447
448    public static String checkValidation(String name) {
449        String fullname = null;
450        if (name == null) {
451            return null;
452        }
453        try {
454            // get full path name
455            fullname = (new File(name)).getCanonicalPath();
456            File fCheck = new File(fullname);
457            if (!(fCheck.exists())) {
458                // get absolute directory
459                File temp = new File(fCheck.getParent());
460                // test if the directory exists
461                if (!(temp.isDirectory()))
462                    fullname = null;
463                temp = null;
464            }
465            fCheck = null;
466
467        } catch (IOException e) {
468            fullname = null; // invalid name
469        }
470        return fullname;
471    }
472
473
474    private static String exec(String c) {
475        StringTokenizer st = new StringTokenizer(c);
476        Vector<String> v = new Vector<>();
477        while (st.hasMoreTokens()) {
478            v.addElement(st.nextToken());
479        }
480        final String[] command = new String[v.size()];
481        v.copyInto(command);
482        try {
483
484            Process p =
485                java.security.AccessController.doPrivileged
486                (new java.security.PrivilegedAction<Process> () {
487                        public Process run() {
488                            try {
489                                return (Runtime.getRuntime().exec(command));
490                            } catch (java.io.IOException e) {
491                                if (DEBUG) {
492                                    e.printStackTrace();
493                                }
494                                return null;
495                            }
496                        }
497                    });
498            if (p == null) {
499                // exception occurred during executing the command
500                return null;
501            }
502
503            BufferedReader commandResult =
504                new BufferedReader
505                    (new InputStreamReader(p.getInputStream(), "8859_1"));
506            String s1 = null;
507            if ((command.length == 1) &&
508                (command[0].equals("/usr/bin/env"))) {
509                while ((s1 = commandResult.readLine()) != null) {
510                    if (s1.length() >= 11) {
511                        if ((s1.substring(0, 11)).equalsIgnoreCase
512                            ("KRB5CCNAME=")) {
513                            s1 = s1.substring(11);
514                            break;
515                        }
516                    }
517                }
518            } else     s1 = commandResult.readLine();
519            commandResult.close();
520            return s1;
521        } catch (Exception e) {
522            if (DEBUG) {
523                e.printStackTrace();
524            }
525        }
526        return null;
527    }
528}
529