1 /* 2 * Copyright (c) 2003, 2013, 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 31 package sun.security.krb5.internal.tools; 32 33 import sun.security.krb5.*; 34 import sun.security.krb5.internal.ktab.*; 35 import java.io.IOException; 36 import java.io.BufferedReader; 37 import java.io.InputStreamReader; 38 import java.io.File; 39 import java.text.DateFormat; 40 import java.util.Arrays; 41 import java.util.Date; 42 import java.util.Locale; 43 import sun.security.krb5.internal.crypto.EType; 44 /** 45 * This class can execute as a command-line tool to help the user manage 46 * entries in the key table. 47 * Available functions include list/add/update/delete service key(s). 48 * 49 * @author Yanni Zhang 50 * @author Ram Marti 51 */ 52 53 public class Ktab { 54 // KeyTabAdmin admin; 55 KeyTab table; 56 char action; 57 String name; // name and directory of key table 58 String principal; 59 boolean showEType; 60 boolean showTime; 61 int etype = -1; 62 char[] password = null; 63 64 boolean forced = false; // true if delete without prompt. Default false 65 boolean append = false; // true if new keys are appended. Default false 66 int vDel = -1; // kvno to delete, -1 all, -2 old. Default -1 67 int vAdd = -1; // kvno to add. Default -1, means auto incremented 68 69 /** 70 * The main program that can be invoked at command line. 71 * See {@link #printHelp} for usages. 72 */ 73 public static void main(String[] args) { 74 Ktab ktab = new Ktab(); 75 if ((args.length == 1) && (args[0].equalsIgnoreCase("-help"))) { 76 ktab.printHelp(); 77 return; 78 } else if ((args == null) || (args.length == 0)) { 79 ktab.action = 'l'; 80 } else { 81 ktab.processArgs(args); 82 } 83 ktab.table = KeyTab.getInstance(ktab.name); 84 if (ktab.table.isMissing() && ktab.action != 'a') { 85 if (ktab.name == null) { 86 System.out.println("No default key table exists."); 87 } else { 88 System.out.println("Key table " + 89 ktab.name + " does not exist."); 90 } 91 System.exit(-1); 92 } 93 if (!ktab.table.isValid()) { 94 if (ktab.name == null) { 95 System.out.println("The format of the default key table " + 96 " is incorrect."); 97 } else { 98 System.out.println("The format of key table " + 99 ktab.name + " is incorrect."); 100 } 101 System.exit(-1); 102 } 103 switch (ktab.action) { 104 case 'l': 105 ktab.listKt(); 106 break; 107 case 'a': 108 ktab.addEntry(); 109 break; 110 case 'd': 111 ktab.deleteEntry(); 112 break; 113 default: 114 ktab.error("A command must be provided"); 115 } 116 } 117 118 /** 119 * Parses the command line arguments. 120 */ 121 void processArgs(String[] args) { 122 123 // Commands (should appear before options): 124 // -l 125 // -a <princ> 126 // -d <princ> 127 // Options: 128 // -e <etype> (for -d) 129 // -e (for -l) 130 // -n <kvno> 131 // -k <keytab> 132 // -t 133 // -f 134 // -append 135 // Optional extra arguments: 136 // password for -a 137 // [kvno|all|old] for -d 138 139 boolean argAlreadyAppeared = false; 140 for (int i = 0; i < args.length; i++) { 141 if (args[i].startsWith("-")) { 142 switch (args[i].toLowerCase(Locale.US)) { 143 144 // Commands 145 case "-l": // list 146 action = 'l'; 147 break; 148 case "-a": // add a new entry to keytab. 149 action = 'a'; 150 if (++i >= args.length || args[i].startsWith("-")) { 151 error("A principal name must be specified after -a"); 152 } 153 principal = args[i]; 154 break; 155 case "-d": // delete entries 156 action = 'd'; 157 if (++i >= args.length || args[i].startsWith("-")) { 158 error("A principal name must be specified after -d"); 159 } 160 principal = args[i]; 161 break; 162 163 // Options 164 case "-e": 165 if (action == 'l') { // list etypes 166 showEType = true; 167 } else if (action == 'd') { // delete etypes 168 if (++i >= args.length || args[i].startsWith("-")) { 169 error("An etype must be specified after -e"); 170 } 171 try { 172 etype = Integer.parseInt(args[i]); 173 if (etype <= 0) { 174 throw new NumberFormatException(); 175 } 176 } catch (NumberFormatException nfe) { 177 error(args[i] + " is not a valid etype"); 178 } 179 } else { 180 error(args[i] + " is not valid after -" + action); 181 } 182 break; 183 case "-n": // kvno for -a 184 if (++i >= args.length || args[i].startsWith("-")) { 185 error("A KVNO must be specified after -n"); 186 } 187 try { 188 vAdd = Integer.parseInt(args[i]); 189 if (vAdd < 0) { 190 throw new NumberFormatException(); 191 } 192 } catch (NumberFormatException nfe) { 193 error(args[i] + " is not a valid KVNO"); 194 } 195 break; 196 case "-k": // specify keytab to use 197 if (++i >= args.length || args[i].startsWith("-")) { 198 error("A keytab name must be specified after -k"); 199 } 200 if (args[i].length() >= 5 && 201 args[i].substring(0, 5).equalsIgnoreCase("FILE:")) { 202 name = args[i].substring(5); 203 } else { 204 name = args[i]; 205 } 206 break; 207 case "-t": // list timestamps 208 showTime = true; 209 break; 210 case "-f": // force delete, no prompt 211 forced = true; 212 break; 213 case "-append": // -a, new keys append to file 214 append = true; 215 break; 216 default: 217 error("Unknown command: " + args[i]); 218 break; 219 } 220 } else { // optional standalone arguments 221 if (argAlreadyAppeared) { 222 error("Useless extra argument " + args[i]); 223 } 224 if (action == 'a') { 225 password = args[i].toCharArray(); 226 } else if (action == 'd') { 227 switch (args[i]) { 228 case "all": vDel = -1; break; 229 case "old": vDel = -2; break; 230 default: { 231 try { 232 vDel = Integer.parseInt(args[i]); 233 if (vDel < 0) { 234 throw new NumberFormatException(); 235 } 236 } catch (NumberFormatException nfe) { 237 error(args[i] + " is not a valid KVNO"); 238 } 239 } 240 } 241 } else { 242 error("Useless extra argument " + args[i]); 243 } 244 argAlreadyAppeared = true; 245 } 246 } 247 } 248 249 /** 250 * Adds a service key to key table. If the specified key table does not 251 * exist, the program will automatically generate 252 * a new key table. 253 */ 254 void addEntry() { 255 PrincipalName pname = null; 256 try { 257 pname = new PrincipalName(principal); 258 } catch (KrbException e) { 259 System.err.println("Failed to add " + principal + 260 " to keytab."); 261 e.printStackTrace(); 262 System.exit(-1); 263 } 264 if (password == null) { 265 try { 266 BufferedReader cis = 267 new BufferedReader(new InputStreamReader(System.in)); 268 System.out.print("Password for " + pname.toString() + ":"); 269 System.out.flush(); 270 password = cis.readLine().toCharArray(); 271 } catch (IOException e) { 272 System.err.println("Failed to read the password."); 273 e.printStackTrace(); 274 System.exit(-1); 275 } 276 277 } 278 try { 279 // admin.addEntry(pname, password); 280 table.addEntry(pname, password, vAdd, append); 281 Arrays.fill(password, '0'); // clear password 282 // admin.save(); 283 table.save(); 284 System.out.println("Done!"); 285 System.out.println("Service key for " + principal + 286 " is saved in " + table.tabName()); 287 288 } catch (KrbException e) { 289 System.err.println("Failed to add " + principal + " to keytab."); 290 e.printStackTrace(); 291 System.exit(-1); 292 } catch (IOException e) { 293 System.err.println("Failed to save new entry."); 294 e.printStackTrace(); 295 System.exit(-1); 296 } 297 } 298 299 /** 300 * Lists key table name and entries in it. 301 */ 302 void listKt() { 303 System.out.println("Keytab name: " + table.tabName()); 304 KeyTabEntry[] entries = table.getEntries(); 305 if ((entries != null) && (entries.length > 0)) { 306 String[][] output = new String[entries.length+1][showTime?3:2]; 307 int column = 0; 308 output[0][column++] = "KVNO"; 309 if (showTime) output[0][column++] = "Timestamp"; 310 output[0][column++] = "Principal"; 311 for (int i = 0; i < entries.length; i++) { 312 column = 0; 313 output[i+1][column++] = entries[i].getKey(). 314 getKeyVersionNumber().toString(); 315 if (showTime) output[i+1][column++] = 316 DateFormat.getDateTimeInstance( 317 DateFormat.SHORT, DateFormat.SHORT).format( 318 new Date(entries[i].getTimeStamp().getTime())); 319 String princ = entries[i].getService().toString(); 320 if (showEType) { 321 int e = entries[i].getKey().getEType(); 322 output[i+1][column++] = princ + " (" + e + ":" + 323 EType.toString(e) + ")"; 324 } else { 325 output[i+1][column++] = princ; 326 } 327 } 328 int[] width = new int[column]; 329 for (int j=0; j<column; j++) { 330 for (int i=0; i <= entries.length; i++) { 331 if (output[i][j].length() > width[j]) { 332 width[j] = output[i][j].length(); 333 } 334 } 335 if (j != 0) width[j] = -width[j]; 336 } 337 for (int j=0; j<column; j++) { 338 System.out.printf("%" + width[j] + "s ", output[0][j]); 339 } 340 System.out.println(); 341 for (int j=0; j<column; j++) { 342 for (int k=0; k<Math.abs(width[j]); k++) System.out.print("-"); 343 System.out.print(" "); 344 } 345 System.out.println(); 346 for (int i=0; i<entries.length; i++) { 347 for (int j=0; j<column; j++) { 348 System.out.printf("%" + width[j] + "s ", output[i+1][j]); 349 } 350 System.out.println(); 351 } 352 } else { 353 System.out.println("0 entry."); 354 } 355 } 356 357 /** 358 * Deletes an entry from the key table. 359 */ 360 void deleteEntry() { 361 PrincipalName pname = null; 362 try { 363 pname = new PrincipalName(principal); 364 if (!forced) { 365 String answer; 366 BufferedReader cis = 367 new BufferedReader(new InputStreamReader(System.in)); 368 System.out.print("Are you sure you want to delete "+ 369 "service key(s) for " + pname.toString() + 370 " (" + (etype==-1?"all etypes":("etype="+etype)) + ", " + 371 (vDel==-1?"all kvno":(vDel==-2?"old kvno":("kvno=" + vDel))) + 372 ") in " + table.tabName() + "? (Y/[N]): "); 373 374 System.out.flush(); 375 answer = cis.readLine(); 376 if (answer.equalsIgnoreCase("Y") || 377 answer.equalsIgnoreCase("Yes")); 378 else { 379 // no error, the user did not want to delete the entry 380 System.exit(0); 381 } 382 } 383 } catch (KrbException e) { 384 System.err.println("Error occurred while deleting the entry. "+ 385 "Deletion failed."); 386 e.printStackTrace(); 387 System.exit(-1); 388 } catch (IOException e) { 389 System.err.println("Error occurred while deleting the entry. "+ 390 " Deletion failed."); 391 e.printStackTrace(); 392 System.exit(-1); 393 } 394 395 int count = table.deleteEntries(pname, etype, vDel); 396 397 if (count == 0) { 398 System.err.println("No matched entry in the keytab. " + 399 "Deletion fails."); 400 System.exit(-1); 401 } else { 402 try { 403 table.save(); 404 } catch (IOException e) { 405 System.err.println("Error occurs while saving the keytab. " + 406 "Deletion fails."); 407 e.printStackTrace(); 408 System.exit(-1); 409 } 410 System.out.println("Done! " + count + " entries removed."); 411 } 412 } 413 414 void error(String... errors) { 415 for (String error: errors) { 416 System.out.println("Error: " + error + "."); 417 } 418 printHelp(); 419 System.exit(-1); 420 } 421 /** 422 * Prints out the help information. 423 */ 424 void printHelp() { 425 System.out.println("\nUsage: ktab <commands> <options>"); 426 System.out.println(); 427 System.out.println("Available commands:"); 428 System.out.println(); 429 System.out.println("-l [-e] [-t]\n" 430 + " list the keytab name and entries. -e with etype, -t with timestamp."); 431 System.out.println("-a <principal name> [<password>] [-n <kvno>] [-append]\n" 432 + " add new key entries to the keytab for the given principal name with\n" 433 + " optional <password>. If a <kvno> is specified, new keys' Key Version\n" 434 + " Numbers equal to the value, otherwise, automatically incrementing\n" 435 + " the Key Version Numbers. If -append is specified, new keys are\n" 436 + " appended to the keytab, otherwise, old keys for the\n" 437 + " same principal are removed."); 438 System.out.println("-d <principal name> [-f] [-e <etype>] [<kvno> | all | old]\n" 439 + " delete key entries from the keytab for the specified principal. If\n" 440 + " <kvno> is specified, delete keys whose Key Version Numbers match\n" 441 + " kvno. If \"all\" is specified, delete all keys. If \"old\" is specified,\n" 442 + " delete all keys except those with the highest kvno. Default action\n" 443 + " is \"all\". If <etype> is specified, only keys of this encryption type\n" 444 + " are deleted. <etype> should be specified as the numberic value etype\n" 445 + " defined in RFC 3961, section 8. A prompt to confirm the deletion is\n" 446 + " displayed unless -f is specified."); 447 System.out.println(); 448 System.out.println("Common option(s):"); 449 System.out.println(); 450 System.out.println("-k <keytab name>\n" 451 + " specify keytab name and path with prefix FILE:"); 452 } 453 }