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 }