blob: cb4ded6e668df105aab4371cb16ed63832f4e218 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Portions Copyright 2000-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 sun.security.krb5.*;
34import sun.security.krb5.internal.*;
35import sun.security.krb5.internal.ccache.*;
36import java.io.BufferedReader;
37import java.io.InputStreamReader;
38import java.io.IOException;
39import java.util.StringTokenizer;
40import java.io.File;
41import java.util.Arrays;
42import sun.security.util.Password;
43
44/**
45 * Kinit tool for obtaining Kerberos v5 tickets.
46 *
47 * @author Yanni Zhang
48 * @author Ram Marti
49 */
50public class Kinit {
51
52 private KinitOptions options;
53 private static final boolean DEBUG = Krb5.DEBUG;
54
55 /**
56 * The main method is used to accept user command line input for ticket
57 * request.
58 * <p>
59 * Usage: kinit [-A] [-f] [-p] [-c cachename] [[-k [-t keytab_file_name]]
60 * [principal] [password]
61 * <ul>
62 * <li> -A do not include addresses
63 * <li> -f forwardable
64 * <li> -p proxiable
65 * <li> -c cache name (i.e., FILE://c:\temp\mykrb5cc)
66 * <li> -k use keytab
67 * <li> -t keytab file name
68 * <li> principal the principal name (i.e., duke@java.sun.com)
69 * <li> password the principal's Kerberos password
70 * </ul>
71 * <p>
72 * Use java sun.security.krb5.tools.Kinit -help to bring up help menu.
73 * <p>
74 * We currently support only file-based credentials cache to
75 * store the tickets obtained from the KDC.
76 * By default, for all Unix platforms a cache file named
77 * /tmp/krb5cc_&lt;uid&gt will be generated. The &lt;uid&gt is the
78 * numeric user identifier.
79 * For all other platforms, a cache file named
80 * &lt;USER_HOME&gt/krb5cc_&lt;USER_NAME&gt would be generated.
81 * <p>
82 * &lt;USER_HOME&gt is obtained from <code>java.lang.System</code>
83 * property <i>user.home</i>.
84 * &lt;USER_NAME&gt is obtained from <code>java.lang.System</code>
85 * property <i>user.name</i>.
86 * If &lt;USER_HOME&gt is null the cache file would be stored in
87 * the current directory that the program is running from.
88 * &lt;USER_NAME&gt is operating system's login username.
89 * It could be different from user's principal name.
90 *</p>
91 *<p>
92 * For instance, on Windows NT, it could be
93 * c:\winnt\profiles\duke\krb5cc_duke, in
94 * which duke is the &lt;USER_NAME&gt, and c:\winnt\profile\duke is the
95 * &lt;USER_HOME&gt.
96 *<p>
97 * A single user could have multiple principal names,
98 * but the primary principal of the credentials cache could only be one,
99 * which means one cache file could only store tickets for one
100 * specific user principal. If the user switches
101 * the principal name at the next Kinit, the cache file generated for the
102 * new ticket would overwrite the old cache file by default.
103 * To avoid overwriting, you need to specify
104 * a different cache file name when you request a
105 * new ticket.
106 *</p>
107 *<p>
108 * You can specify the location of the cache file by using the -c option
109 *
110 */
111
112 public static void main(String[] args) {
113 try {
114 Kinit self = new Kinit(args);
115 }
116 catch (Exception e) {
117 String msg = null;
118 if (e instanceof KrbException) {
119 msg = ((KrbException)e).krbErrorMessage() + " " +
120 ((KrbException)e).returnCodeMessage();
121 } else {
122 msg = e.getMessage();
123 }
124 if (msg != null) {
125 System.err.println("Exception: " + msg);
126 } else {
127 System.out.println("Exception: " + e);
128 }
129 e.printStackTrace();
130 System.exit(-1);
131 }
132 return;
133 }
134
135 /**
136 * Constructs a new Kinit object.
137 * @param args array of ticket request options.
138 * Avaiable options are: -f, -p, -c, principal, password.
139 * @exception IOException if an I/O error occurs.
140 * @exception RealmException if the Realm could not be instantiated.
141 * @exception KrbException if error occurs during Kerberos operation.
142 */
143 private Kinit(String[] args)
144 throws IOException, RealmException, KrbException {
145 if (args == null || args.length == 0) {
146 options = new KinitOptions();
147 } else {
148 options = new KinitOptions(args);
149 }
150 String princName = null;
151 PrincipalName principal = options.getPrincipal();
152 if (principal != null) {
153 princName = principal.toString();
154 }
155 if (DEBUG) {
156 System.out.println("Principal is " + principal);
157 }
158 char[] psswd = options.password;
159 EncryptionKey[] skeys = null;
160 boolean useKeytab = options.useKeytabFile();
161 if (!useKeytab) {
162 if (princName == null) {
163 throw new IllegalArgumentException
164 (" Can not obtain principal name");
165 }
166 if (psswd == null) {
167 System.out.print("Password for " + princName + ":");
168 System.out.flush();
169 psswd = Password.readPassword(System.in);
170 if (DEBUG) {
171 System.out.println(">>> Kinit console input " +
172 new String(psswd));
173 }
174 }
175 } else {
176 if (DEBUG) {
177 System.out.println(">>> Kinit using keytab");
178 }
179 if (princName == null) {
180 throw new IllegalArgumentException
181 ("Principal name must be specified.");
182 }
183 String ktabName = options.keytabFileName();
184 if (ktabName != null) {
185 if (DEBUG) {
186 System.out.println(
187 ">>> Kinit keytab file name: " + ktabName);
188 }
189 }
190
191 // assert princName and principal are nonnull
192 skeys = EncryptionKey.acquireSecretKeys(principal, ktabName);
193
194 if (skeys == null || skeys.length == 0) {
195 String msg = "No supported key found in keytab";
196 if (princName != null) {
197 msg += " for principal " + princName;
198 }
199 throw new KrbException(msg);
200 }
201 }
202
203 KDCOptions opt = new KDCOptions();
204 setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
205 setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
206 String realm = options.getKDCRealm();
207 if (realm == null) {
208 realm = Config.getInstance().getDefaultRealm();
209 }
210
211 if (DEBUG) {
212 System.out.println(">>> Kinit realm name is " + realm);
213 }
214
215 PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm,
216 PrincipalName.KRB_NT_SRV_INST);
217 sname.setRealm(realm);
218
219 if (DEBUG) {
220 System.out.println(">>> Creating KrbAsReq");
221 }
222
223 KrbAsReq as_req = null;
224 HostAddresses addresses = null;
225 try {
226 if (options.getAddressOption())
227 addresses = HostAddresses.getLocalAddresses();
228
229 if (useKeytab) {
230 as_req = new KrbAsReq(skeys, opt,
231 principal, sname,
232 null, null, null, null, addresses, null);
233 } else {
234 as_req = new KrbAsReq(psswd, opt,
235 principal, sname,
236 null, null, null, null, addresses, null);
237 }
238 } catch (KrbException exc) {
239 throw exc;
240 } catch (Exception exc) {
241 throw new KrbException(exc.toString());
242 }
243
244 KrbAsRep as_rep = null;
245 try {
246 as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
247 } catch (KrbException ke) {
248 if ((ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) ||
249 (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
250 if (DEBUG) {
251 System.out.println("Kinit: PREAUTH FAILED/REQ, re-send AS-REQ");
252 }
253 KRBError error = ke.getError();
254 int etype = error.getEType();
255 byte[] salt = error.getSalt();
256 byte[] s2kparams = error.getParams();
257 if (useKeytab) {
258 as_req = new KrbAsReq(skeys, true, etype, salt, s2kparams,
259 opt, principal, sname,
260 null, null, null, null, addresses, null);
261 } else {
262 as_req = new KrbAsReq(psswd, true, etype, salt, s2kparams,
263 opt, principal, sname,
264 null, null, null, null, addresses, null);
265 }
266 as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
267 } else {
268 throw ke;
269 }
270 }
271
272 sun.security.krb5.internal.ccache.Credentials credentials =
273 as_rep.setCredentials();
274 // we always create a new cache and store the ticket we get
275 CredentialsCache cache =
276 CredentialsCache.create(principal, options.cachename);
277 if (cache == null) {
278 throw new IOException("Unable to create the cache file " +
279 options.cachename);
280 }
281 cache.update(credentials);
282 cache.save();
283
284 if (options.password == null) {
285 // Assume we're running interactively
286 System.out.println("New ticket is stored in cache file " +
287 options.cachename);
288 } else {
289 Arrays.fill(options.password, '0');
290 }
291
292 // clear the password
293 if (psswd != null) {
294 Arrays.fill(psswd, '0');
295 }
296 options = null; // release reference to options
297 }
298
299 private static KrbAsRep sendASRequest(KrbAsReq as_req, boolean useKeytab,
300 String realm, char[] passwd, EncryptionKey[] skeys)
301 throws IOException, RealmException, KrbException {
302
303 if (DEBUG) {
304 System.out.println(">>> Kinit: sending as_req to realm " + realm);
305 }
306
307 String kdc = as_req.send(realm);
308
309 if (DEBUG) {
310 System.out.println(">>> reading response from kdc");
311 }
312 KrbAsRep as_rep = null;
313 try {
314 if (useKeytab) {
315 as_rep = as_req.getReply(skeys);
316 } else {
317 as_rep = as_req.getReply(passwd);
318 }
319 } catch (KrbException ke) {
320 if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
321 as_req.send(realm, kdc, true); // useTCP is set
322 if (useKeytab) {
323 as_rep = as_req.getReply(skeys);
324 } else {
325 as_rep = as_req.getReply(passwd);
326 }
327 } else {
328 throw ke;
329 }
330 }
331 return as_rep;
332 }
333
334 private static void setOptions(int flag, int option, KDCOptions opt) {
335 switch (option) {
336 case 0:
337 break;
338 case -1:
339 opt.set(flag, false);
340 break;
341 case 1:
342 opt.set(flag, true);
343 }
344 }
345}