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