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 31package sun.security.krb5.internal.tools; 32 33import java.io.File; 34import sun.security.krb5.*; 35import sun.security.krb5.internal.*; 36import sun.security.krb5.internal.ccache.*; 37import java.io.IOException; 38import java.util.Arrays; 39import sun.security.util.Password; 40import 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 */ 48public 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 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 */ 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 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 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 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