001: /*
002: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: /*
027: * (C) Copyright IBM Corp. 1999 All Rights Reserved.
028: * Copyright 1997 The Open Group Research Institute. All rights reserved.
029: */
030:
031: package sun.security.krb5.internal.tools;
032:
033: import sun.security.krb5.*;
034: import sun.security.krb5.internal.*;
035: import sun.security.krb5.internal.ccache.*;
036: import java.io.BufferedReader;
037: import java.io.InputStreamReader;
038: import java.io.IOException;
039: import java.util.StringTokenizer;
040: import java.io.File;
041: import java.util.Arrays;
042: import sun.security.util.Password;
043:
044: /**
045: * Kinit tool for obtaining Kerberos v5 tickets.
046: *
047: * @author Yanni Zhang
048: * @author Ram Marti
049: * @version 1.00 12 Apr 2000
050: */
051: public class Kinit {
052:
053: private KinitOptions options;
054: private static final boolean DEBUG = Krb5.DEBUG;
055:
056: /**
057: * The main method is used to accept user command line input for ticket
058: * request.
059: * <p>
060: * Usage: kinit [-A] [-f] [-p] [-c cachename] [[-k [-t keytab_file_name]]
061: * [principal] [password]
062: * <ul>
063: * <li> -A do not include addresses
064: * <li> -f forwardable
065: * <li> -p proxiable
066: * <li> -c cache name (i.e., FILE://c:\temp\mykrb5cc)
067: * <li> -k use keytab
068: * <li> -t keytab file name
069: * <li> principal the principal name (i.e., duke@java.sun.com)
070: * <li> password the principal's Kerberos password
071: * </ul>
072: * <p>
073: * Use java sun.security.krb5.tools.Kinit -help to bring up help menu.
074: * <p>
075: * We currently support only file-based credentials cache to
076: * store the tickets obtained from the KDC.
077: * By default, for all Unix platforms a cache file named
078: * /tmp/krb5cc_<uid> will be generated. The <uid> is the
079: * numeric user identifier.
080: * For all other platforms, a cache file named
081: * <USER_HOME>/krb5cc_<USER_NAME> would be generated.
082: * <p>
083: * <USER_HOME> is obtained from <code>java.lang.System</code>
084: * property <i>user.home</i>.
085: * <USER_NAME> is obtained from <code>java.lang.System</code>
086: * property <i>user.name</i>.
087: * If <USER_HOME> is null the cache file would be stored in
088: * the current directory that the program is running from.
089: * <USER_NAME> is operating system's login username.
090: * It could be different from user's principal name.
091: *</p>
092: *<p>
093: * For instance, on Windows NT, it could be
094: * c:\winnt\profiles\duke\krb5cc_duke, in
095: * which duke is the <USER_NAME>, and c:\winnt\profile\duke is the
096: * <USER_HOME>.
097: *<p>
098: * A single user could have multiple principal names,
099: * but the primary principal of the credentials cache could only be one,
100: * which means one cache file could only store tickets for one
101: * specific user principal. If the user switches
102: * the principal name at the next Kinit, the cache file generated for the
103: * new ticket would overwrite the old cache file by default.
104: * To avoid overwriting, you need to specify
105: * a different cache file name when you request a
106: * new ticket.
107: *</p>
108: *<p>
109: * You can specify the location of the cache file by using the -c option
110: *
111: */
112:
113: public static void main(String[] args) {
114: try {
115: Kinit self = new Kinit(args);
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) throws IOException, RealmException,
144: 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(">>> Kinit keytab file name: "
187: + ktabName);
188: }
189: }
190:
191: // assert princName and principal are nonnull
192: skeys = EncryptionKey
193: .acquireSecretKeys(principal, ktabName);
194:
195: if (skeys == null || skeys.length == 0) {
196: String msg = "No supported key found in keytab";
197: if (princName != null) {
198: msg += " for principal " + princName;
199: }
200: throw new KrbException(msg);
201: }
202: }
203:
204: KDCOptions opt = new KDCOptions();
205: setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
206: setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
207: String realm = options.getKDCRealm();
208: if (realm == null) {
209: realm = Config.getInstance().getDefaultRealm();
210: }
211:
212: if (DEBUG) {
213: System.out.println(">>> Kinit realm name is " + realm);
214: }
215:
216: PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm,
217: PrincipalName.KRB_NT_SRV_INST);
218: sname.setRealm(realm);
219:
220: if (DEBUG) {
221: System.out.println(">>> Creating KrbAsReq");
222: }
223:
224: KrbAsReq as_req = null;
225: HostAddresses addresses = null;
226: try {
227: if (options.getAddressOption())
228: addresses = HostAddresses.getLocalAddresses();
229:
230: if (useKeytab) {
231: as_req = new KrbAsReq(skeys, opt, principal, sname,
232: null, null, null, null, addresses, null);
233: } else {
234: as_req = new KrbAsReq(psswd, opt, principal, sname,
235: null, null, null, null, addresses, null);
236: }
237: } catch (KrbException exc) {
238: throw exc;
239: } catch (Exception exc) {
240: throw new KrbException(exc.toString());
241: }
242:
243: KrbAsRep as_rep = null;
244: try {
245: as_rep = sendASRequest(as_req, useKeytab, realm, psswd,
246: 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
252: .println("Kinit: PREAUTH FAILED/REQ, re-send AS-REQ");
253: }
254: KRBError error = ke.getError();
255: int etype = error.getEType();
256: byte[] salt = error.getSalt();
257: byte[] s2kparams = error.getParams();
258: if (useKeytab) {
259: as_req = new KrbAsReq(skeys, true, etype, salt,
260: s2kparams, opt, principal, sname, null,
261: null, null, null, addresses, null);
262: } else {
263: as_req = new KrbAsReq(psswd, true, etype, salt,
264: s2kparams, opt, principal, sname, null,
265: null, null, null, addresses, null);
266: }
267: as_rep = sendASRequest(as_req, useKeytab, realm, psswd,
268: skeys);
269: } else {
270: throw ke;
271: }
272: }
273:
274: sun.security.krb5.internal.ccache.Credentials credentials = as_rep
275: .setCredentials();
276: // we always create a new cache and store the ticket we get
277: CredentialsCache cache = CredentialsCache.create(principal,
278: options.cachename);
279: if (cache == null) {
280: throw new IOException("Unable to create the cache file "
281: + options.cachename);
282: }
283: cache.update(credentials);
284: cache.save();
285:
286: if (options.password == null) {
287: // Assume we're running interactively
288: System.out.println("New ticket is stored in cache file "
289: + options.cachename);
290: } else {
291: Arrays.fill(options.password, '0');
292: }
293:
294: // clear the password
295: if (psswd != null) {
296: Arrays.fill(psswd, '0');
297: }
298: options = null; // release reference to options
299: }
300:
301: private static KrbAsRep sendASRequest(KrbAsReq as_req,
302: boolean useKeytab, String realm, char[] passwd,
303: EncryptionKey[] skeys) throws IOException, RealmException,
304: KrbException {
305:
306: if (DEBUG) {
307: System.out.println(">>> Kinit: sending as_req to realm "
308: + realm);
309: }
310:
311: String kdc = as_req.send(realm);
312:
313: if (DEBUG) {
314: System.out.println(">>> reading response from kdc");
315: }
316: KrbAsRep as_rep = null;
317: try {
318: if (useKeytab) {
319: as_rep = as_req.getReply(skeys);
320: } else {
321: as_rep = as_req.getReply(passwd);
322: }
323: } catch (KrbException ke) {
324: if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
325: as_req.send(realm, kdc, true); // useTCP is set
326: if (useKeytab) {
327: as_rep = as_req.getReply(skeys);
328: } else {
329: as_rep = as_req.getReply(passwd);
330: }
331: } else {
332: throw ke;
333: }
334: }
335: return as_rep;
336: }
337:
338: private static void setOptions(int flag, int option, KDCOptions opt) {
339: switch (option) {
340: case 0:
341: break;
342: case -1:
343: opt.set(flag, false);
344: break;
345: case 1:
346: opt.set(flag, true);
347: }
348: }
349: }
|