/* * 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); } } }