/*
* Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
* (C) Copyright IBM Corp. 1999 All Rights Reserved.
* Copyright 1997 The Open Group Research Institute. All rights reserved.
*/
package sun.security.krb5.internal.tools;
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.io.File;
import java.util.Arrays;
import sun.security.util.Password;
/**
* Kinit tool for obtaining Kerberos v5 tickets.
*
* @author Yanni Zhang
* @author Ram Marti
*/
public class Kinit {
private KinitOptions options;
private static final boolean DEBUG = Krb5.DEBUG;
/**
* The main method is used to accept user command line input for ticket
* request.
* <p>
* Usage: kinit [-A] [-f] [-p] [-c cachename] [[-k [-t keytab_file_name]]
* [principal] [password]
* <ul>
* <li> -A do not include addresses
* <li> -f forwardable
* <li> -p proxiable
* <li> -c cache name (i.e., FILE://c:\temp\mykrb5cc)
* <li> -k use keytab
* <li> -t keytab file name
* <li> principal the principal name (i.e., duke@java.sun.com)
* <li> password the principal's Kerberos password
* </ul>
* <p>
* Use java sun.security.krb5.tools.Kinit -help to bring up help menu.
* <p>
* We currently support only file-based credentials cache to
* store the tickets obtained from the KDC.
* By default, for all Unix platforms a cache file named
* /tmp/krb5cc_<uid> will be generated. The <uid> is the
* numeric user identifier.
* For all other platforms, a cache file named
* <USER_HOME>/krb5cc_<USER_NAME> would be generated.
* <p>
* <USER_HOME> is obtained from <code>java.lang.System</code>
* property <i>user.home</i>.
* <USER_NAME> is obtained from <code>java.lang.System</code>
* property <i>user.name</i>.
* If <USER_HOME> is null the cache file would be stored in
* the current directory that the program is running from.
* <USER_NAME> is operating system's login username.
* It could be different from user's principal name.
*</p>
*<p>
* For instance, on Windows NT, it could be
* c:\winnt\profiles\duke\krb5cc_duke, in
* which duke is the <USER_NAME>, and c:\winnt\profile\duke is the
* <USER_HOME>.
*<p>
* A single user could have multiple principal names,
* but the primary principal of the credentials cache could only be one,
* which means one cache file could only store tickets for one
* specific user principal. If the user switches
* the principal name at the next Kinit, the cache file generated for the
* new ticket would overwrite the old cache file by default.
* To avoid overwriting, you need to specify
* a different cache file name when you request a
* new ticket.
*</p>
*<p>
* You can specify the location of the cache file by using the -c option
*
*/
public static void main(String[] args) {
try {
Kinit self = new Kinit(args);
}
catch (Exception e) {
String msg = null;
if (e instanceof KrbException) {
msg = ((KrbException)e).krbErrorMessage() + " " +
((KrbException)e).returnCodeMessage();
} else {
msg = e.getMessage();
}
if (msg != null) {
System.err.println("Exception: " + msg);
} else {
System.out.println("Exception: " + e);
}
e.printStackTrace();
System.exit(-1);
}
return;
}
/**
* Constructs a new Kinit object.
* @param args array of ticket request options.
* Avaiable options are: -f, -p, -c, principal, password.
* @exception IOException if an I/O error occurs.
* @exception RealmException if the Realm could not be instantiated.
* @exception KrbException if error occurs during Kerberos operation.
*/
private Kinit(String[] args)
throws IOException, RealmException, KrbException {
if (args == null || args.length == 0) {
options = new KinitOptions();
} else {
options = new KinitOptions(args);
}
String princName = null;
PrincipalName principal = options.getPrincipal();
if (principal != null) {
princName = principal.toString();
}
if (DEBUG) {
System.out.println("Principal is " + principal);
}
char[] psswd = options.password;
EncryptionKey[] skeys = null;
boolean useKeytab = options.useKeytabFile();
if (!useKeytab) {
if (princName == null) {
throw new IllegalArgumentException
(" Can not obtain principal name");
}
if (psswd == null) {
System.out.print("Password for " + princName + ":");
System.out.flush();
psswd = Password.readPassword(System.in);
if (DEBUG) {
System.out.println(">>> Kinit console input " +
new String(psswd));
}
}
} else {
if (DEBUG) {
System.out.println(">>> Kinit using keytab");
}
if (princName == null) {
throw new IllegalArgumentException
("Principal name must be specified.");
}
String ktabName = options.keytabFileName();
if (ktabName != null) {
if (DEBUG) {
System.out.println(
">>> Kinit keytab file name: " + ktabName);
}
}
// assert princName and principal are nonnull
skeys = EncryptionKey.acquireSecretKeys(principal, ktabName);
if (skeys == null || skeys.length == 0) {
String msg = "No supported key found in keytab";
if (princName != null) {
msg += " for principal " + princName;
}
throw new KrbException(msg);
}
}
KDCOptions opt = new KDCOptions();
setOptions(KDCOptions.FORWARDABLE, options.forwardable, opt);
setOptions(KDCOptions.PROXIABLE, options.proxiable, opt);
String realm = options.getKDCRealm();
if (realm == null) {
realm = Config.getInstance().getDefaultRealm();
}
if (DEBUG) {
System.out.println(">>> Kinit realm name is " + realm);
}
PrincipalName sname = new PrincipalName("krbtgt" + "/" + realm,
PrincipalName.KRB_NT_SRV_INST);
sname.setRealm(realm);
if (DEBUG) {
System.out.println(">>> Creating KrbAsReq");
}
KrbAsReq as_req = null;
HostAddresses addresses = null;
try {
if (options.getAddressOption())
addresses = HostAddresses.getLocalAddresses();
if (useKeytab) {
as_req = new KrbAsReq(skeys, opt,
principal, sname,
null, null, null, null, addresses, null);
} else {
as_req = new KrbAsReq(psswd, opt,
principal, sname,
null, null, null, null, addresses, null);
}
} catch (KrbException exc) {
throw exc;
} catch (Exception exc) {
throw new KrbException(exc.toString());
}
KrbAsRep as_rep = null;
try {
as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
} catch (KrbException ke) {
if ((ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) ||
(ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
if (DEBUG) {
System.out.println("Kinit: PREAUTH FAILED/REQ, re-send AS-REQ");
}
KRBError error = ke.getError();
int etype = error.getEType();
byte[] salt = error.getSalt();
byte[] s2kparams = error.getParams();
if (useKeytab) {
as_req = new KrbAsReq(skeys, true, etype, salt, s2kparams,
opt, principal, sname,
null, null, null, null, addresses, null);
} else {
as_req = new KrbAsReq(psswd, true, etype, salt, s2kparams,
opt, principal, sname,
null, null, null, null, addresses, null);
}
as_rep = sendASRequest(as_req, useKeytab, realm, psswd, skeys);
} else {
throw ke;
}
}
sun.security.krb5.internal.ccache.Credentials credentials =
as_rep.setCredentials();
// we always create a new cache and store the ticket we get
CredentialsCache cache =
CredentialsCache.create(principal, options.cachename);
if (cache == null) {
throw new IOException("Unable to create the cache file " +
options.cachename);
}
cache.update(credentials);
cache.save();
if (options.password == null) {
// Assume we're running interactively
System.out.println("New ticket is stored in cache file " +
options.cachename);
} else {
Arrays.fill(options.password, '0');
}
// clear the password
if (psswd != null) {
Arrays.fill(psswd, '0');
}
options = null; // release reference to options
}
private static KrbAsRep sendASRequest(KrbAsReq as_req, boolean useKeytab,
String realm, char[] passwd, EncryptionKey[] skeys)
throws IOException, RealmException, KrbException {
if (DEBUG) {
System.out.println(">>> Kinit: sending as_req to realm " + realm);
}
String kdc = as_req.send(realm);
if (DEBUG) {
System.out.println(">>> reading response from kdc");
}
KrbAsRep as_rep = null;
try {
if (useKeytab) {
as_rep = as_req.getReply(skeys);
} else {
as_rep = as_req.getReply(passwd);
}
} catch (KrbException ke) {
if (ke.returnCode() == Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
as_req.send(realm, kdc, true); // useTCP is set
if (useKeytab) {
as_rep = as_req.getReply(skeys);
} else {
as_rep = as_req.getReply(passwd);
}
} else {
throw ke;
}
}
return as_rep;
}
private static void setOptions(int flag, int option, KDCOptions opt) {
switch (option) {
case 0:
break;
case -1:
opt.set(flag, false);
break;
case 1:
opt.set(flag, true);
}
}
}