1/*
2 * Copyright (c) 2000, 2012, 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 *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
28 *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
29 */
30
31package sun.security.krb5.internal.tools;
32
33import java.io.File;
34import sun.security.krb5.*;
35import sun.security.krb5.internal.*;
36import sun.security.krb5.internal.ccache.*;
37import java.io.IOException;
38import java.util.Arrays;
39import sun.security.util.Password;
40import javax.security.auth.kerberos.KeyTab;
41
42/**
43 * Kinit tool for obtaining Kerberos v5 tickets.
44 *
45 * @author Yanni Zhang
46 * @author Ram Marti
47 */
48public class Kinit {
49
50    private KinitOptions options;
51    private static final boolean DEBUG = Krb5.DEBUG;
52
53    /**
54     * The main method is used to accept user command line input for ticket
55     * request. Read {@link KinitOptions#printHelp} for usages or call
56     *    java sun.security.krb5.internal.tools.Kinit -help
57     * to bring up help menu.
58     * <p>
59     * We currently support only file-based credentials cache to
60     * store the tickets obtained from the KDC.
61     * By default, for all Unix platforms a cache file named
62     * {@code /tmp/krb5cc_<uid>} will be generated. The {@code <uid>} is the
63     * numeric user identifier.
64     * For all other platforms, a cache file named
65     * {@code <USER_HOME>/krb5cc_<USER_NAME>} would be generated.
66     * <p>
67     * {@code <USER_HOME>} is obtained from {@code java.lang.System}
68     * property <i>user.home</i>.
69     * {@code <USER_NAME>} is obtained from {@code java.lang.System}
70     * property <i>user.name</i>.
71     * If {@code <USER_HOME>} is null the cache file would be stored in
72     * the current directory that the program is running from.
73     * {@code <USER_NAME>} is operating system's login username.
74     * It could be different from user's principal name.
75     * <p>
76     * For instance, on Windows NT, it could be
77     * {@code c:\winnt\profiles\duke\krb5cc_duke}, in
78     * which duke is the {@code <USER_NAME>} and {@code c:\winnt\profile\duke} is the
79     * {@code <USER_HOME>}.
80     * <p>
81     * A single user could have multiple principal names,
82     * but the primary principal of the credentials cache could only be one,
83     * which means one cache file could only store tickets for one
84     * specific user principal. If the user switches
85     * the principal name at the next Kinit, the cache file generated for the
86     * new ticket would overwrite the old cache file by default.
87     * To avoid overwriting, you need to specify
88     * a different cache file name when you request a
89     * new ticket.
90     * <p>
91     * You can specify the location of the cache file by using the -c option
92     */
93
94    public static void main(String[] args) {
95        try {
96            Kinit self = new Kinit(args);
97        }
98        catch (Exception e) {
99            String msg = null;
100            if (e instanceof KrbException) {
101                msg = ((KrbException)e).krbErrorMessage() + " " +
102                    ((KrbException)e).returnCodeMessage();
103            } else  {
104                msg = e.getMessage();
105            }
106            if (msg != null) {
107                System.err.println("Exception: " + msg);
108            } else {
109                System.out.println("Exception: " + e);
110            }
111            e.printStackTrace();
112            System.exit(-1);
113        }
114        return;
115    }
116
117    /**
118     * Constructs a new Kinit object.
119     * @param args array of ticket request options.
120     * Avaiable options are: -f, -p, -c, principal, password.
121     * @exception IOException if an I/O error occurs.
122     * @exception RealmException if the Realm could not be instantiated.
123     * @exception KrbException if error occurs during Kerberos operation.
124     */
125    private Kinit(String[] args)
126        throws IOException, RealmException, KrbException {
127        if (args == null || args.length == 0) {
128            options = new KinitOptions();
129        } else {
130            options = new KinitOptions(args);
131        }
132        switch (options.action) {
133            case 1:
134                acquire();
135                break;
136            case 2:
137                renew();
138                break;
139            default:
140                throw new KrbException("kinit does not support action "
141                        + options.action);
142        }
143    }
144
145    private void renew()
146            throws IOException, RealmException, KrbException {
147
148        PrincipalName principal = options.getPrincipal();
149        String realm = principal.getRealmAsString();
150        CredentialsCache cache = CredentialsCache.getInstance(options.cachename);
151
152        if (cache == null) {
153            throw new IOException("Unable to find existing cache file " +
154                    options.cachename);
155        }
156        sun.security.krb5.internal.ccache.Credentials credentials =
157                cache.getCreds(PrincipalName.tgsService(realm, realm));
158
159        credentials = credentials.setKrbCreds()
160                .renew()
161                .toCCacheCreds();
162
163        cache = CredentialsCache.create(principal, options.cachename);
164        if (cache == null) {
165            throw new IOException("Unable to create the cache file " +
166                    options.cachename);
167        }
168        cache.update(credentials);
169        cache.save();
170    }
171
172    private void acquire()
173            throws IOException, RealmException, KrbException {
174
175        String princName = null;
176        PrincipalName principal = options.getPrincipal();
177        if (principal != null) {
178            princName = principal.toString();
179        }
180        KrbAsReqBuilder builder;
181        if (DEBUG) {
182            System.out.println("Principal is " + principal);
183        }
184        char[] psswd = options.password;
185        boolean useKeytab = options.useKeytabFile();
186        if (!useKeytab) {
187            if (princName == null) {
188                throw new IllegalArgumentException
189                    (" Can not obtain principal name");
190            }
191            if (psswd == null) {
192                System.out.print("Password for " + princName + ":");
193                System.out.flush();
194                psswd = Password.readPassword(System.in);
195                if (DEBUG) {
196                    System.out.println(">>> Kinit console input " +
197                        new String(psswd));
198                }
199            }
200            builder = new KrbAsReqBuilder(principal, psswd);
201        } else {
202            if (DEBUG) {
203                System.out.println(">>> Kinit using keytab");
204            }
205            if (princName == null) {
206                throw new IllegalArgumentException
207                    ("Principal name must be specified.");
208            }
209            String ktabName = options.keytabFileName();
210            if (ktabName != null) {
211                if (DEBUG) {
212                    System.out.println(
213                                       ">>> Kinit keytab file name: " + ktabName);
214                }
215            }
216
217            builder = new KrbAsReqBuilder(principal, ktabName == null
218                    ? KeyTab.getInstance()
219                    : KeyTab.getInstance(new File(ktabName)));
220        }
221
222        KDCOptions opt = new KDCOptions();
223        setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
224        setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
225        builder.setOptions(opt);
226        String realm = options.getKDCRealm();
227        if (realm == null) {
228            realm = Config.getInstance().getDefaultRealm();
229        }
230
231        if (DEBUG) {
232            System.out.println(">>> Kinit realm name is " + realm);
233        }
234
235        PrincipalName sname = PrincipalName.tgsService(realm, realm);
236        builder.setTarget(sname);
237
238        if (DEBUG) {
239            System.out.println(">>> Creating KrbAsReq");
240        }
241
242        if (options.getAddressOption())
243            builder.setAddresses(HostAddresses.getLocalAddresses());
244
245        builder.setTill(options.lifetime);
246        builder.setRTime(options.renewable_lifetime);
247
248        builder.action();
249
250        sun.security.krb5.internal.ccache.Credentials credentials =
251            builder.getCCreds();
252        builder.destroy();
253
254        // we always create a new cache and store the ticket we get
255        CredentialsCache cache =
256            CredentialsCache.create(principal, options.cachename);
257        if (cache == null) {
258           throw new IOException("Unable to create the cache file " +
259                                 options.cachename);
260        }
261        cache.update(credentials);
262        cache.save();
263
264        if (options.password == null) {
265            // Assume we're running interactively
266            System.out.println("New ticket is stored in cache file " +
267                               options.cachename);
268         } else {
269             Arrays.fill(options.password, '0');
270         }
271
272        // clear the password
273        if (psswd != null) {
274            Arrays.fill(psswd, '0');
275        }
276        options = null; // release reference to options
277    }
278
279    private static void setOptions(int flag, int option, KDCOptions opt) {
280        switch (option) {
281        case 0:
282            break;
283        case -1:
284            opt.set(flag, false);
285            break;
286        case 1:
287            opt.set(flag, true);
288        }
289    }
290}
291