/*
* Copyright (c) 2003, 2011, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.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.ktab.*;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.File;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import sun.security.krb5.internal.crypto.EType;
/**
* This class can execute as a command-line tool to help the user manage
* entries in the key table.
* Available functions include list/add/update/delete service key(s).
*
* @author Yanni Zhang
* @author Ram Marti
*/
public class Ktab {
// KeyTabAdmin admin;
KeyTab table;
char action;
String name; // name and directory of key table
String principal;
boolean showEType;
boolean showTime;
int etype = -1;
char[] password = null;
boolean forced = false; // true if delete without prompt. Default false
boolean append = false; // true if new keys are appended. Default false
int vDel = -1; // kvno to delete, -1 all, -2 old. Default -1
int vAdd = -1; // kvno to add. Default -1, means auto incremented
/**
* The main program that can be invoked at command line.
* See {@link #printHelp} for usages.
*/
public static void main(String[] args) {
Ktab ktab = new Ktab();
if ((args.length == 1) && (args[0].equalsIgnoreCase("-help"))) {
ktab.printHelp();
return;
} else if ((args == null) || (args.length == 0)) {
ktab.action = 'l';
} else {
ktab.processArgs(args);
}
ktab.table = KeyTab.getInstance(ktab.name);
if (ktab.table.isMissing() && ktab.action != 'a') {
if (ktab.name == null) {
System.out.println("No default key table exists.");
} else {
System.out.println("Key table " +
ktab.name + " does not exist.");
}
System.exit(-1);
}
if (!ktab.table.isValid()) {
if (ktab.name == null) {
System.out.println("The format of the default key table " +
" is incorrect.");
} else {
System.out.println("The format of key table " +
ktab.name + " is incorrect.");
}
System.exit(-1);
}
switch (ktab.action) {
case 'l':
ktab.listKt();
break;
case 'a':
ktab.addEntry();
break;
case 'd':
ktab.deleteEntry();
break;
default:
ktab.error("A command must be provided");
}
}
/**
* Parses the command line arguments.
*/
void processArgs(String[] args) {
// Commands (should appear before options):
// -l
// -a <princ>
// -d <princ>
// Options:
// -e <etype> (for -d)
// -e (for -l)
// -n <kvno>
// -k <keytab>
// -t
// -f
// -append
// Optional extra arguments:
// password for -a
// [kvno|all|old] for -d
boolean argAlreadyAppeared = false;
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
switch (args[i].toLowerCase(Locale.US)) {
// Commands
case "-l": // list
action = 'l';
break;
case "-a": // add a new entry to keytab.
action = 'a';
if (++i >= args.length || args[i].startsWith("-")) {
error("A principal name must be specified after -a");
}
principal = args[i];
break;
case "-d": // delete entries
action = 'd';
if (++i >= args.length || args[i].startsWith("-")) {
error("A principal name must be specified after -d");
}
principal = args[i];
break;
// Options
case "-e":
if (action == 'l') { // list etypes
showEType = true;
} else if (action == 'd') { // delete etypes
if (++i >= args.length || args[i].startsWith("-")) {
error("An etype must be specified after -e");
}
try {
etype = Integer.parseInt(args[i]);
if (etype <= 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException nfe) {
error(args[i] + " is not a valid etype");
}
} else {
error(args[i] + " is not valid after -" + action);
}
break;
case "-n": // kvno for -a
if (++i >= args.length || args[i].startsWith("-")) {
error("A KVNO must be specified after -n");
}
try {
vAdd = Integer.parseInt(args[i]);
if (vAdd < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException nfe) {
error(args[i] + " is not a valid KVNO");
}
break;
case "-k": // specify keytab to use
if (++i >= args.length || args[i].startsWith("-")) {
error("A keytab name must be specified after -k");
}
if (args[i].length() >= 5 &&
args[i].substring(0, 5).equalsIgnoreCase("FILE:")) {
name = args[i].substring(5);
} else {
name = args[i];
}
break;
case "-t": // list timestamps
showTime = true;
break;
case "-f": // force delete, no prompt
forced = true;
break;
case "-append": // -a, new keys append to file
append = true;
break;
default:
error("Unknown command: " + args[i]);
break;
}
} else { // optional standalone arguments
if (argAlreadyAppeared) {
error("Useless extra argument " + args[i]);
}
if (action == 'a') {
password = args[i].toCharArray();
} else if (action == 'd') {
switch (args[i]) {
case "all": vDel = -1; break;
case "old": vDel = -2; break;
default: {
try {
vDel = Integer.parseInt(args[i]);
if (vDel < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException nfe) {
error(args[i] + " is not a valid KVNO");
}
}
}
} else {
error("Useless extra argument " + args[i]);
}
argAlreadyAppeared = true;
}
}
}
/**
* Adds a service key to key table. If the specified key table does not
* exist, the program will automatically generate
* a new key table.
*/
void addEntry() {
PrincipalName pname = null;
try {
pname = new PrincipalName(principal);
if (pname.getRealm() == null) {
pname.setRealm(Config.getInstance().getDefaultRealm());
}
} catch (KrbException e) {
System.err.println("Failed to add " + principal +
" to keytab.");
e.printStackTrace();
System.exit(-1);
}
if (password == null) {
try {
BufferedReader cis =
new BufferedReader(new InputStreamReader(System.in));
System.out.print("Password for " + pname.toString() + ":");
System.out.flush();
password = cis.readLine().toCharArray();
} catch (IOException e) {
System.err.println("Failed to read the password.");
e.printStackTrace();
System.exit(-1);
}
}
try {
// admin.addEntry(pname, password);
table.addEntry(pname, password, vAdd, append);
Arrays.fill(password, '0'); // clear password
// admin.save();
table.save();
System.out.println("Done!");
System.out.println("Service key for " + principal +
" is saved in " + table.tabName());
} catch (KrbException e) {
System.err.println("Failed to add " + principal + " to keytab.");
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
System.err.println("Failed to save new entry.");
e.printStackTrace();
System.exit(-1);
}
}
/**
* Lists key table name and entries in it.
*/
void listKt() {
System.out.println("Keytab name: " + table.tabName());
KeyTabEntry[] entries = table.getEntries();
if ((entries != null) && (entries.length > 0)) {
String[][] output = new String[entries.length+1][showTime?3:2];
int column = 0;
output[0][column++] = "KVNO";
if (showTime) output[0][column++] = "Timestamp";
output[0][column++] = "Principal";
for (int i = 0; i < entries.length; i++) {
column = 0;
output[i+1][column++] = entries[i].getKey().
getKeyVersionNumber().toString();
if (showTime) output[i+1][column++] =
DateFormat.getDateTimeInstance(
DateFormat.SHORT, DateFormat.SHORT).format(
new Date(entries[i].getTimeStamp().getTime()));
String princ = entries[i].getService().toString();
if (showEType) {
int e = entries[i].getKey().getEType();
output[i+1][column++] = princ + " (" + e + ":" +
EType.toString(e) + ")";
} else {
output[i+1][column++] = princ;
}
}
int[] width = new int[column];
for (int j=0; j<column; j++) {
for (int i=0; i <= entries.length; i++) {
if (output[i][j].length() > width[j]) {
width[j] = output[i][j].length();
}
}
if (j != 0) width[j] = -width[j];
}
for (int j=0; j<column; j++) {
System.out.printf("%" + width[j] + "s ", output[0][j]);
}
System.out.println();
for (int j=0; j<column; j++) {
for (int k=0; k<Math.abs(width[j]); k++) System.out.print("-");
System.out.print(" ");
}
System.out.println();
for (int i=0; i<entries.length; i++) {
for (int j=0; j<column; j++) {
System.out.printf("%" + width[j] + "s ", output[i+1][j]);
}
System.out.println();
}
} else {
System.out.println("0 entry.");
}
}
/**
* Deletes an entry from the key table.
*/
void deleteEntry() {
PrincipalName pname = null;
try {
pname = new PrincipalName(principal);
if (pname.getRealm() == null) {
pname.setRealm(Config.getInstance().getDefaultRealm());
}
if (!forced) {
String answer;
BufferedReader cis =
new BufferedReader(new InputStreamReader(System.in));
System.out.print("Are you sure you want to delete "+
"service key(s) for " + pname.toString() +
" (" + (etype==-1?"all etypes":("etype="+etype)) + ", " +
(vDel==-1?"all kvno":(vDel==-2?"old kvno":("kvno=" + vDel))) +
") in " + table.tabName() + "? (Y/[N]): ");
System.out.flush();
answer = cis.readLine();
if (answer.equalsIgnoreCase("Y") ||
answer.equalsIgnoreCase("Yes"));
else {
// no error, the user did not want to delete the entry
System.exit(0);
}
}
} catch (KrbException e) {
System.err.println("Error occured while deleting the entry. "+
"Deletion failed.");
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
System.err.println("Error occured while deleting the entry. "+
" Deletion failed.");
e.printStackTrace();
System.exit(-1);
}
int count = table.deleteEntries(pname, etype, vDel);
if (count == 0) {
System.err.println("No matched entry in the keytab. " +
"Deletion fails.");
System.exit(-1);
} else {
try {
table.save();
} catch (IOException e) {
System.err.println("Error occurs while saving the keytab. " +
"Deletion fails.");
e.printStackTrace();
System.exit(-1);
}
System.out.println("Done! " + count + " entries removed.");
}
}
void error(String... errors) {
for (String error: errors) {
System.out.println("Error: " + error + ".");
}
printHelp();
System.exit(-1);
}
/**
* Prints out the help information.
*/
void printHelp() {
System.out.println("\nUsage: ktab <commands> <options>");
System.out.println();
System.out.println("Available commands:");
System.out.println();
System.out.println("-l [-e] [-t]\n"
+ " list the keytab name and entries. -e with etype, -t with timestamp.");
System.out.println("-a <principal name> [<password>] [-n <kvno>] [-append]\n"
+ " add new key entries to the keytab for the given principal name with\n"
+ " optional <password>. If a <kvno> is specified, new keys' Key Version\n"
+ " Numbers equal to the value, otherwise, automatically incrementing\n"
+ " the Key Version Numbers. If -append is specified, new keys are\n"
+ " appended to the keytab, otherwise, old keys for the\n"
+ " same principal are removed.");
System.out.println("-d <principal name> [-f] [-e <etype>] [<kvno> | all | old]\n"
+ " delete key entries from the keytab for the specified principal. If\n"
+ " <kvno> is specified, delete keys whose Key Version Numbers match\n"
+ " kvno. If \"all\" is specified, delete all keys. If \"old\" is specified,\n"
+ " delete all keys except those with the highest kvno. Default action\n"
+ " is \"all\". If <etype> is specified, only keys of this encryption type\n"
+ " are deleted. <etype> should be specified as the numberic value etype\n"
+ " defined in RFC 3961, section 8. A prompt to confirm the deletion is\n"
+ " displayed unless -f is specified.");
System.out.println();
System.out.println("Common option(s):");
System.out.println();
System.out.println("-k <keytab name>\n"
+ " specify keytab name and path with prefix FILE:");
}
}