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 
31 package sun.security.krb5.internal.tools;
32 
33 import java.io.File;
34 import sun.security.krb5.*;
35 import sun.security.krb5.internal.*;
36 import sun.security.krb5.internal.ccache.*;
37 import java.io.IOException;
38 import java.util.Arrays;
39 import sun.security.util.Password;
40 import 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  */
48 public 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 
main(String[] args)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      */
Kinit(String[] args)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 
renew()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 
acquire()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 
setOptions(int flag, int option, KDCOptions opt)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