1 /*
   2  * Copyright (c) 2003, 2017, 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 }