* kinit.c: Major revamp to support Kerberos 4 compatibility. Code
authorDanilo Almeida <dalmeida@mit.edu>
Fri, 4 Feb 2000 21:26:02 +0000 (21:26 +0000)
committerDanilo Almeida <dalmeida@mit.edu>
Fri, 4 Feb 2000 21:26:02 +0000 (21:26 +0000)
restructured to allow changes to support Kerberos 4 or Kerberos 5
only operation depending on whether dynamic libraries are
avialable.  Explicit documentation and support files to make it
easy to do this will be forthcoming.

* Makefile.in: On Windows, use getopt.lib instead of getopt.obj,
and add support for getopt_long.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@11993 dc483132-0cff-0310-8789-dd5450dbe970

src/clients/kinit/ChangeLog
src/clients/kinit/Makefile.in
src/clients/kinit/kinit.c

index 39d7ec8956f4f9cdd0a19ab686ad6931f1cc4e84..0c8a52e0b224873486e47e523ccad0a399826973 100644 (file)
@@ -1,3 +1,14 @@
+2000-02-04  Danilo Almeida  <dalmeida@mit.edu>
+
+       * kinit.c: Major revamp to support Kerberos 4 compatibility.  Code
+       restructured to allow changes to support Kerberos 4 or Kerberos 5
+       only operation depending on whether dynamic libraries are
+       avialable.  Explicit documentation and support files to make it
+       easy to do this will be forthcoming.
+
+       * Makefile.in: On Windows, use getopt.lib instead of getopt.obj, 
+       and add support for getopt_long.
+
 1999-12-03  Danilo Almeida  <dalmeida@mit.edu>
 
        * Makefile.in: Windows fix for 10/26/99 cleanup.
index 2308ca22d5eb4813648a8bec2bd9bee7a2d180a3..cee5fbde7eca0e2184787d9cc0a19a474be1873c 100644 (file)
@@ -6,6 +6,9 @@ BUILDTOP=$(REL)$(U)$(S)$(U)
 PROG_LIBPATH=-L$(TOPLIBD)
 PROG_RPATH=$(KRB5_LIBDIR)
 
+##WIN32##LOCALINCLUDES=-I$(BUILDTOP)\util\windows
+##WIN32##DEFINES=-DGETOPT_LONG
+
 all-unix:: kinit
 all-windows:: $(OUTPRE)kinit.exe
 all-mac::
@@ -13,8 +16,8 @@ all-mac::
 kinit: kinit.o $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o $@ kinit.o $(KRB5_BASE_LIBS)
 
-$(OUTPRE)kinit.exe: $(OUTPRE)kinit.obj $(BUILDTOP)\util\windows\$(OUTPRE)getopt.obj $(KLIB) $(CLIB)
-       link $(EXE_LINKOPTS) -out:$@ $**
+$(OUTPRE)kinit.exe: $(OUTPRE)kinit.obj $(BUILDTOP)\util\windows\$(OUTPRE)getopt.lib $(KLIB) $(CLIB)
+       link $(EXE_LINKOPTS) -out:$@ $** advapi32.lib
 
 clean-unix::
        $(RM) kinit.o kinit
index 777b44e0dc38c37b7e5073d5b79e16b6a283ffbb..df162ca51efba436059b32b837e6bcd03a9b2b71 100644 (file)
  */
 
 #include <krb5.h>
+#ifdef KRB5_KRB4_COMPAT
+#include <kerberosIV/krb.h>
+#define HAVE_KRB524
+#else
+#undef HAVE_KRB524
+#endif
 #include <string.h>
 #include <stdio.h>
+#include <time.h>
 
 #ifdef GETOPT_LONG
-#include "getopt.h"
+#include <getopt.h>
 #else
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
@@ -45,40 +52,105 @@ extern char *optarg;
 extern int optind;
 extern char *optarg;
 extern int getopt();
+#endif /* HAVE_UNISTD_H */
+#endif /* GETOPT_LONG */
+
+#ifndef _WIN32
+#define GET_PROGNAME(x) (strrchr((x), '/') ? strrchr((x), '/')+1 : (x))
+#else
+#define GET_PROGNAME(x) (max(strrchr((x), '/'), strrchr((x), '\\')) + 1, (x))
 #endif
-#endif
-#include "com_err.h"
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
-
-void get_name_from_passwd_file(program_name, kcontext, me)
-    char * program_name;
-    krb5_context kcontext;
-    krb5_principal * me;
+char * get_name_from_os()
 {
     struct passwd *pw;
-    krb5_error_code code;
-    if (pw = getpwuid((int) getuid())) {
-       if ((code = krb5_parse_name(kcontext, pw->pw_name, me))) {
-           com_err (program_name, code, "when parsing name %s", pw->pw_name);
-           exit(1);
-       }
+    if (pw = getpwuid((int) getuid()))
+       return pw->pw_name;
+    return 0;
+}
+#else /* HAVE_PWD_H */
+#ifdef _WIN32
+char * get_name_from_os()
+{
+    static char name[1024];
+    DWORD name_size = sizeof(name);
+    if (GetUserName(name, &name_size)) {
+       name[sizeof(name)-1] = 0; /* Just to be extra safe */
+       return name;
     } else {
-       fprintf(stderr, "Unable to identify user from password file\n");
-       exit(1);
+       return 0;
     }
 }
-#else /* HAVE_PWD_H */
-void get_name_from_passwd_file(kcontext, me)
-    krb5_context kcontext;
-    krb5_principal * me;
+#else /* _WIN32 */
+char * get_name_from_os()
 {
-    fprintf(stderr, "Unable to identify user\n");
-    exit(1);
+    return 0;
 }
+#endif /* _WIN32 */
 #endif /* HAVE_PWD_H */
 
+static char *progname;
+
+static char* progname_v5 = 0;
+static char* progname_v4 = 0;
+static char* progname_v524 = 0;
+
+static int got_k4 = 0;
+static int got_k5 = 0;
+
+static int authed_k5 = 0;
+static int authed_k4 = 0;
+
+#define KRB4_BACKUP_DEFAULT_LIFE_SECS 10*60*60 /* 10 hours */
+
+typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
+
+struct k_opts
+{
+    /* in seconds */
+    krb5_deltat starttime;
+    krb5_deltat lifetime;
+    krb5_deltat rlife;
+
+    int forwardable;
+    int proxiable;
+    int addresses;
+
+    int not_forwardable;
+    int not_proxiable;
+    int no_addresses;
+
+    int verbose;
+
+    char* principal_name;
+    char* service_name;
+    char* keytab_name;
+    char* cache_name;
+
+    action_type action;
+};
+
+struct k5_data
+{
+    krb5_context ctx;
+    krb5_ccache cc;
+    krb5_principal me;
+    char* name;
+};
+
+struct k4_data
+{
+    krb5_deltat lifetime;
+#ifdef KRB5_KRB4_COMPAT
+    char aname[ANAME_SZ + 1];
+    char inst[INST_SZ + 1];
+    char realm[REALM_SZ + 1];
+    char name[ANAME_SZ + 1 + INST_SZ + 1 + REALM_SZ + 1];
+#endif
+};
+
 #ifdef GETOPT_LONG
 /* if struct[2] == NULL, then long_getopt acts as if the short flag
    struct[3] was specified.  If struct[2] != NULL, then struct[3] is
@@ -86,184 +158,220 @@ void get_name_from_passwd_file(kcontext, me)
    stored in *index, and long_getopt() returns 0. */
 
 struct option long_options[] = {
-    { "noforwardable", 0, NULL, 'f'+0200 },
-    { "noproxiable", 0, NULL, 'p'+0200 },
-    { "addresses", 0, NULL, 'A'+0200},
+    { "noforwardable", 0, NULL, 'F' },
+    { "noproxiable", 0, NULL, 'P' },
+    { "addresses", 0, NULL, 'a'},
     { "forwardable", 0, NULL, 'f' },
     { "proxiable", 0, NULL, 'p' },
-    { "noaddresses", 0, NULL, 'A'},
-    { "version", 0, NULL, 0x01 },
+    { "noaddresses", 0, NULL, 'A' },
     { NULL, 0, NULL, 0 }
 };
+
+#define GETOPT(argc, argv, str) getopt_long(argc, argv, str, long_options, 0)
+#define USAGE_LONG_FORWARDABLE " | --forwardable | --noforwardable"
+#define USAGE_LONG_PROXIABLE   " | --proxiable | --noproxiable"
+#define USAGE_LONG_ADDRESSES   " | --addresses | --noaddresses"
+#else
+#define GETOPT(argc, argv, str) getopt(argc, argv, str)
+#define USAGE_LONG_FORWARDABLE ""
+#define USAGE_LONG_PROXIABLE   ""
+#define USAGE_LONG_ADDRESSES   ""
 #endif
 
-int
-main(argc, argv)
+void
+usage()
+{
+#ifdef KRB5_KRB4_COMPAT
+#define USAGE_K54_OPT       "[-4] [-5] "
+#define USAGE_K54_SRVTAB    "/srvtab"
+#else
+#define USAGE_K54_OPT       ""
+#define USAGE_K54_SRVTAB    ""
+#endif
+
+    fprintf(stderr, "Usage: %s [-V] " USAGE_K54_OPT
+           "[-l lifetime] [-r renewable_life] "
+           "[-f | -F" USAGE_LONG_FORWARDABLE "] "
+           "[-p | -P" USAGE_LONG_PROXIABLE "] "
+           "[-A" USAGE_LONG_ADDRESSES "] "
+           "[-s start_time] [-S target_service] "
+           "[-k [-t keytab_file]] [-R] [-v] [-c cachename] [principal]\n", 
+           progname);
+    fprintf(stderr,
+#ifdef KRB5_KRB4_COMPAT
+            "\t-4 Kerberos 4 only, -5 Kerberos 5 only, default is both\n"
+            "\toptions applicable to Kerberos 5 only:\n"
+#endif
+            "\t\t-v validate\n"
+            "\t\t-c cache name\n"
+            "\t\t-f forwardable\n"
+            "\t\t-F not forwardable\n"
+            "\t\t-p proxiable\n"
+            "\t\t-P not proxiable\n"
+           "\t\t-A do not include addresses\n"
+            "\t\t-r renewable lifetime\n"
+            "\t\t-s start time\n"
+#ifdef KRB5_KRB4_COMPAT
+            "\toptions potentially applicable to both:\n"
+#endif
+            "\t\t-R renew\n"
+            "\t\t-l lifetime\n"
+            "\t\t-S service\n"
+            "\t\t-k use keytab" USAGE_K54_SRVTAB "\n"
+            "\t\t-t filename of keytab" USAGE_K54_SRVTAB " to use\n"
+            "\t\t-V verbose\n"
+        );
+    exit(2);
+}
+
+char *
+parse_options(argc, argv, opts)
     int argc;
     char **argv;
+    struct k_opts* opts;
 {
-    krb5_context kcontext;
-    krb5_principal me = NULL;
-    krb5_deltat start_time = 0;
-    krb5_address **addresses = NULL;
-    krb5_get_init_creds_opt opts;
-    char *service_name = NULL;
-    krb5_keytab keytab = NULL;
-    char *cache_name = NULL;
-    krb5_ccache ccache = NULL;
-    enum { INIT_PW, INIT_KT, RENEW, VALIDATE} action;
-    int errflg = 0, idx, i;
-    krb5_creds my_creds;
     krb5_error_code code;
+    int errflg = 0;
+    int use_k4_only = 0;
+    int use_k5_only = 0;
+    int i;
 
-    /* Ensure we can be driven from a pipe */
-    if(!isatty(fileno(stdin)))
-        setvbuf(stdin, 0, _IONBF, 0);
-    if(!isatty(fileno(stdout)))
-        setvbuf(stdout, 0, _IONBF, 0);
-    if(!isatty(fileno(stderr)))
-        setvbuf(stderr, 0, _IONBF, 0);
-
-    if (code = krb5_init_context(&kcontext)) {
-       com_err(argv[0], code, "while initializing kerberos library");
-       exit(1);
-    }
-
-    krb5_get_init_creds_opt_init(&opts);
-
-    action = INIT_PW;
-
-    if (strrchr(argv[0], '/'))
-       argv[0] = strrchr(argv[0], '/')+1;
-
-    while (
-#ifdef GETOPT_LONG
-          (i = getopt_long(argc, argv, "r:fpAl:s:c:kt:RS:v",
-                           long_options, &idx)) != -1
+#ifdef KRB5_KRB4_COMPAT
+#define GETOPT_K54 "45"
 #else
-          (i = getopt(argc, argv, "r:fpAl:s:c:kt:RS:v")) != -1
+#define GETOPT_K54 ""
 #endif
-          ) {
+
+    while ((i = GETOPT(argc, argv, "r:fpFP" GETOPT_K54 "AVl:s:c:kt:RS:v"))
+          != -1) {
        switch (i) {
-#ifdef GETOPT_LONG
-       case 1: /* Print the version */
-           printf("%s\n", krb5_version);
-           exit(0);
-#endif
+        case 'V':
+            opts->verbose = 1;
+            break;
         case 'l':
-           {
-               krb5_deltat lifetime;
-               code = krb5_string_to_deltat(optarg, &lifetime);
-               if (code != 0 || lifetime == 0) {
-                   fprintf(stderr, "Bad lifetime value %s\n", optarg);
-                   errflg++;
-               }
-               krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime);
-           }
-           break;
+            /* Lifetime */
+            code = krb5_string_to_deltat(optarg, &opts->lifetime);
+            if (code != 0 || opts->lifetime == 0) {
+                fprintf(stderr, "Bad lifetime value %s\n", optarg);
+                errflg++;
+            }
+            break;
        case 'r':
-           {
-               krb5_deltat rlife;
-
-               code = krb5_string_to_deltat(optarg, &rlife);
-               if (code != 0 || rlife == 0) {
-                   fprintf(stderr, "Bad lifetime value %s\n", optarg);
-                   errflg++;
-               }
-               krb5_get_init_creds_opt_set_renew_life(&opts, rlife);
-           }
-           break;
+            /* Renewable Time */
+            code = krb5_string_to_deltat(optarg, &opts->rlife);
+            if (code != 0 || opts->rlife == 0) {
+                fprintf(stderr, "Bad lifetime value %s\n", optarg);
+                errflg++;
+            }
+            break;
        case 'f':
-           krb5_get_init_creds_opt_set_forwardable(&opts, 1);
-           break;
-#ifdef GETOPT_LONG
-       case 'f'+0200:
-           krb5_get_init_creds_opt_set_forwardable(&opts, 0);
-           break;
-#endif
+            opts->forwardable = 1;
+            break;
+        case 'F':
+            opts->not_forwardable = 1;
+            break;
        case 'p':
-           krb5_get_init_creds_opt_set_proxiable(&opts, 1);
+            opts->proxiable = 1;
+            break;
+       case 'P':
+            opts->not_proxiable = 1;
+            break;
+       case 'a':
+           /* Note: This is supported only with GETOPT_LONG */
+           opts->addresses = 1;
            break;
-#ifdef GETOPT_LONG
-       case 'p'+0200:
-           krb5_get_init_creds_opt_set_proxiable(&opts, 0);
-           break;
-#endif
        case 'A':
-           krb5_get_init_creds_opt_set_address_list(&opts, NULL);
+           opts->no_addresses = 1;
            break;
-#ifdef GETOPT_LONG
-       case 'A'+0200:
-           krb5_os_localaddr(kcontext, &addresses);
-           krb5_get_init_creds_opt_set_address_list(&opts, addresses);
-           break;
-#endif
                case 's':
-           code = krb5_string_to_deltat(optarg, &start_time);
-           if (code != 0 || start_time == 0) {
+           code = krb5_string_to_deltat(optarg, &opts->starttime);
+           if (code != 0 || opts->starttime == 0) {
                krb5_timestamp abs_starttime;
-               krb5_timestamp now;
 
                code = krb5_string_to_timestamp(optarg, &abs_starttime);
                if (code != 0 || abs_starttime == 0) {
                    fprintf(stderr, "Bad start time value %s\n", optarg);
                    errflg++;
                } else {
-                   if ((code = krb5_timeofday(kcontext, &now))) {
-                       com_err(argv[0], code,
-                               "while getting time of day");
-                       exit(1);
-                   }
-
-                   start_time = abs_starttime - now;
+                   opts->starttime = abs_starttime - time(0);
                }
            }
            break;
         case 'S':
-           service_name = optarg;
+            opts->service_name = optarg;
            break;
         case 'k':
-           action = INIT_KT;
+            opts->action = INIT_KT;
            break;
         case 't':
-           if (keytab == NULL) {
-                code = krb5_kt_resolve(kcontext, optarg, &keytab);
-                if (code != 0) {
-                     com_err(argv[0], code, "resolving keytab %s", optarg);
-                     errflg++;
-                }
-           } else {
-                fprintf(stderr, "Only one -t option allowed.\n");
-                errflg++;
-           }
-           break;
+            if (opts->keytab_name)
+            {
+                fprintf(stderr, "Only one -t option allowed.\n");
+                errflg++;
+            } else {
+                opts->keytab_name = optarg;
+            }
+            break;
        case 'R':
-           action = RENEW;
+           opts->action = RENEW;
            break;
        case 'v':
-           action = VALIDATE;
+           opts->action = VALIDATE;
            break;
                case 'c':
-           if (ccache == NULL) {
-               cache_name = optarg;
-               
-               code = krb5_cc_resolve (kcontext, cache_name, &ccache);
-               if (code != 0) {
-                   com_err (argv[0], code, "resolving ccache %s",
-                            cache_name);
-                   errflg++;
-               }
-           } else {
-               fprintf(stderr, "Only one -c option allowed\n");
-               errflg++;
-           }
+            if (opts->cache_name)
+            {
+                fprintf(stderr, "Only one -c option allowed\n");
+                errflg++;
+            } else {
+                opts->cache_name = optarg;
+            }
            break;
+#ifdef KRB5_KRB4_COMPAT
+        case '4':
+            if (!got_k4)
+            {
+                fprintf(stderr, "Kerberos 4 support could not be loaded\n");
+                exit(3);
+            }
+            use_k4_only = 1;
+            break;
+        case '5':
+            if (!got_k5)
+            {
+                fprintf(stderr, "Kerberos 5 support could not be loaded\n");
+                exit(3);
+            }
+            use_k5_only = 1;
+            break;
+#endif
        default:
            errflg++;
            break;
        }
     }
 
+    if (use_k5_only && use_k4_only)
+    {
+        fprintf(stderr, "Only one of -4 and -5 allowed\n");
+        errflg++;
+    }
+    if (opts->forwardable && opts->not_forwardable)
+    {
+        fprintf(stderr, "Only one of -f and -F allowed\n");
+        errflg++;
+    }
+    if (opts->proxiable && opts->not_proxiable)
+    {
+        fprintf(stderr, "Only one of -p and -P allowed\n");
+        errflg++;
+    }
+    if (opts->addresses && opts->no_addresses)
+    {
+        fprintf(stderr, "Only one of -a and -A allowed\n");
+        errflg++;
+    }
+
     if (argc - optind > 1) {
        fprintf(stderr, "Extra arguments (starting with \"%s\").\n",
                argv[optind+1]);
@@ -271,95 +379,603 @@ main(argc, argv)
     }
 
     if (errflg) {
-#ifdef GETOPT_LONG
-       fprintf(stderr, "Usage: %s [--version] [-l lifetime] [-r renewable_life] [-f | --forwardable | --noforwardable] [-p | --proxiable | --noproxiable] [-A | --noaddresses | --addresses] [-s start_time] [-S target_service] [-k [-t keytab_file]] [-R] [-v] [-c cachename] [principal]\n", argv[0]);
-#else
-       fprintf(stderr, "Usage: %s [-l lifetime] [-r renewable_life] [-f] [-p] [-A] [-s start_time] [-S target_service] [-k [-t keytab_file]] [-R] [-v] [-c cachename] [principal]\n", argv[0]);
-#endif
-       exit(2);
+        usage();
     }
 
-    if (ccache == NULL) {
-        if ((code = krb5_cc_default(kcontext, &ccache))) {
-             com_err(argv[0], code, "while getting default ccache");
-             exit(1);
-        }
+    /* At this point, we know we only have one option selection */
+    if (use_k4_only)
+        got_k5 = 0;
+    if (use_k5_only)
+        got_k4 = 0;
+
+    opts->principal_name = (optind == argc-1) ? argv[optind] : 0;
+    return opts->principal_name;
+}
+
+int
+k5_begin(opts, k5, k4)
+    struct k_opts* opts;
+    struct k5_data* k5;
+    struct k4_data* k4;
+{
+    char* progname = progname_v5;
+    krb5_error_code code = 0;
+
+    if (!got_k5)
+        return 0;
+
+    if (code = krb5_init_context(&k5->ctx)) {
+        com_err(progname, code, "while initializing Kerberos 5 library");
+        return 0;
+    }
+    if (opts->cache_name)
+    {
+        code = krb5_cc_resolve(k5->ctx, opts->cache_name, &k5->cc);
+        if (code != 0) {
+            com_err(progname, code, "resolving ccache %s",
+                     opts->cache_name);
+            return 0;
+        }
+    } 
+    else
+    {
+        if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
+            com_err(progname, code, "while getting default ccache");
+            return 0;
+        }
     }
 
-    if (optind == argc-1) {
-       /* Use specified name */
-       if ((code = krb5_parse_name (kcontext, argv[optind], &me))) {
-           com_err (argv[0], code, "when parsing name %s",argv[optind]);
-           exit(1);
+    if (opts->principal_name)
+    {
+        /* Use specified name */
+        if ((code = krb5_parse_name(k5->ctx, opts->principal_name, 
+                                     &k5->me))) {
+            com_err(progname, code, "when parsing name %s", 
+                     opts->principal_name);
+            return 0;
+        }
+    }
+    else
+    {
+        /* No principal name specified */
+        if (opts->action == INIT_KT) {
+            /* Use the default host/service name */
+            if (code = krb5_sname_to_principal(k5->ctx, NULL, NULL,
+                                              KRB5_NT_SRV_HST, &k5->me)) {
+                com_err(progname, code,
+                       "when creating default server principal name");
+                return 0;
+            }
+        } else {
+            /* Get default principal from cache if one exists */
+            if (code = krb5_cc_get_principal(k5->ctx, k5->cc, 
+                                             &k5->me))
+            {
+                char *name = get_name_from_os();
+                if (!name)
+                {
+                    fprintf(stderr, "Unable to identify user\n");
+                    return 0;
+                }
+                if ((code = krb5_parse_name(k5->ctx, name, 
+                                             &k5->me)))
+                {
+                    com_err(progname, code, "when parsing name %s", 
+                           name);
+                    return 0;
+                }
+            }
+        }
+    }
+    if (code = krb5_unparse_name(k5->ctx, k5->me, 
+                                &k5->name)) {
+        com_err(progname, code, "when unparsing name");
+        return 0;
+    }
+    opts->principal_name = k5->name;
+
+#ifdef KRB5_KRB4_COMPAT
+    if (got_k4)
+    {
+       /* Translate to a Kerberos 4 principal */
+       code = krb5_524_conv_principal(k5->ctx, k5->me,
+                                      k4->aname, k4->inst, k4->realm);
+       if (code) {
+           k4->aname[0] = 0;
+           k4->inst[0] = 0;
+           k4->realm[0] = 0;
        }
+    }
+#endif
+    return 1;
+}
+
+void
+k5_end(k5)
+    struct k5_data* k5;
+{
+    if (k5->name)
+        krb5_free_unparsed_name(k5->ctx, k5->name);
+    if (k5->me)
+        krb5_free_principal(k5->ctx, k5->me);
+    if (k5->cc)
+        krb5_cc_close(k5->ctx, k5->cc);
+    if (k5->ctx)
+        krb5_free_context(k5->ctx);
+    memset(k5, 0, sizeof(*k5));
+}
+
+int
+k4_begin(opts, k4)
+    struct k_opts* opts;
+    struct k4_data* k4;
+{
+    char* progname = progname_v4;
+    int k_errno = 0;
+
+    if (!got_k4)
+        return 0;
+
+#ifdef KRB5_KRB4_COMPAT
+    if (k4->aname[0])
+        goto skip;
+
+    if (opts->principal_name)
+    {
+        /* Use specified name */
+        if (k_errno = kname_parse(k4->aname, k4->inst, k4->realm, 
+                                  opts->principal_name))
+        {
+            fprintf(stderr, "%s: %s\n", progname, 
+                    krb_get_err_text(k_errno));
+            return 0;
+        }
     } else {
-       /* No principal name specified */
-       if (action == INIT_KT) {
-           /* Use the default host/service name */
-           if (code = krb5_sname_to_principal(kcontext, NULL, NULL,
-                                              KRB5_NT_SRV_HST, &me)) {
-               com_err(argv[0], code,
-                       "when creating default server principal name");
-               exit(1);
-           }
-       } else {
-           /* Get default principal from cache if one exists */
-           if (code = krb5_cc_get_principal(kcontext, ccache, &me))
-               get_name_from_passwd_file(argv[0], kcontext, &me);
+        /* No principal name specified */
+        if (opts->action == INIT_KT) {
+            /* Use the default host/service name */
+            /* XXX - need to add this functionality */
+            fprintf(stderr, "%s: Kerberos 4 srvtab support is not "
+                    "implemented\n", progname);
+            return 0;
+        } else {
+            /* Get default principal from cache if one exists */
+            if (k_errno = krb_get_tf_fullname(tkt_string(), k4->aname, 
+                                              k4->inst, k4->realm))
+            {
+                char *name = get_name_from_os();
+                if (!name)
+                {
+                    fprintf(stderr, "Unable to identify user\n");
+                    return 0;
+                }
+                if (k_errno = kname_parse(k4->aname, k4->inst, k4->realm,
+                                          name))
+                {
+                    fprintf(stderr, "%s: %s\n", progname, 
+                            krb_get_err_text(k_errno));
+                    return 0;
+                }
+            }
+        }
+    }
+
+    if (!k4->realm[0])
+        krb_get_lrealm(k4->realm, 1);
+
+    if (k4->inst[0])
+        sprintf(k4->name, "%s.%s@%s", k4->aname, k4->inst, k4->realm);
+    else
+        sprintf(k4->name, "%s@%s", k4->aname, k4->realm);
+    opts->principal_name = k4->name;
+
+ skip:
+    if (k4->aname[0] && !k_isname(k4->aname))
+    {
+       fprintf(stderr, "%s: bad Kerberos 4 name format\n", progname);
+        return 0;
+    }
+
+    if (k4->inst[0] && !k_isinst(k4->inst))
+    {
+        fprintf(stderr, "%s: bad Kerberos 4 instance format\n", progname);
+        return 0;
+    }
+
+    if (k4->realm[0] && !k_isrealm(k4->realm))
+    {
+        fprintf(stderr, "%s: bad Kerberos 4 realm format\n", progname);
+        return 0;
+    }
+#endif /* KRB5_KRB4_COMPAT */
+    return 1;
+}
+
+void
+k4_end(k4)
+    struct k4_data* k4;
+{
+    memset(k4, 0, sizeof(*k4));
+}
+
+int
+k5_kinit(opts, k5, password)
+    struct k_opts* opts;
+    struct k5_data* k5;
+    char* password;
+{
+    char* progname = progname_v5;
+    int notix = 1;
+    krb5_keytab keytab = 0;
+    krb5_creds my_creds;
+    krb5_error_code code = 0;
+    krb5_get_init_creds_opt options;
+
+    if (!got_k5)
+        return 0;
+
+    krb5_get_init_creds_opt_init(&options);
+    memset(&my_creds, 0, sizeof(my_creds));
+
+    /*
+      From this point on, we can goto cleanup because my_creds is
+      initialized.
+    */
+
+    if (opts->lifetime)
+       krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime);
+    if (opts->rlife)
+       krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife);
+    if (opts->forwardable)
+       krb5_get_init_creds_opt_set_forwardable(&options, 1);
+    if (opts->not_forwardable)
+       krb5_get_init_creds_opt_set_forwardable(&options, 0);
+    if (opts->proxiable)
+       krb5_get_init_creds_opt_set_proxiable(&options, 1);
+    if (opts->not_proxiable)
+       krb5_get_init_creds_opt_set_proxiable(&options, 0);
+    if (opts->addresses)
+    {
+       krb5_address **addresses = NULL;
+       code = krb5_os_localaddr(k5->ctx, &addresses);
+       if (code != 0) {
+            com_err(progname, code, "getting local addresses");
+            goto cleanup;
        }
+       krb5_get_init_creds_opt_set_address_list(&options, addresses);
+       krb5_free_addresses(k5->ctx, addresses);
+    }
+    if (opts->no_addresses)
+       krb5_get_init_creds_opt_set_address_list(&options, NULL);
+
+    if ((opts->action == INIT_KT) && opts->keytab_name)
+    {
+        code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab);
+        if (code != 0) {
+            com_err(progname, code, "resolving keytab %s", 
+                     opts->keytab_name);
+            goto cleanup;
+        }
     }
-    
-    switch (action) {
+
+    switch (opts->action) {
     case INIT_PW:
-       code = krb5_get_init_creds_password(kcontext, &my_creds, me, NULL,
-                                           krb5_prompter_posix, NULL,
-                                           start_time, service_name,
-                                           &opts);
+       code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me,
+                                           password, krb5_prompter_posix, 0,
+                                           opts->starttime, 
+                                           opts->service_name,
+                                           &options);
        break;
     case INIT_KT:
-       code = krb5_get_init_creds_keytab(kcontext, &my_creds, me, keytab,
-                                           start_time, service_name,
-                                           &opts);
+       code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me,
+                                         keytab,
+                                         opts->starttime, 
+                                         opts->service_name,
+                                         &options);
        break;
     case VALIDATE:
-       code = krb5_get_validated_creds(kcontext, &my_creds, me, ccache,
-                                       service_name);
+       code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me, k5->cc,
+                                       opts->service_name);
        break;
     case RENEW:
-       code = krb5_get_renewed_creds(kcontext, &my_creds, me, ccache,
-                                     service_name);
+       code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, k5->cc,
+                                     opts->service_name);
        break;
     }
 
     if (code) {
-       if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
-           fprintf (stderr, "%s: Password incorrect\n", argv[0]);
+        char *doing = 0;
+        switch (opts->action) {
+        case INIT_PW:
+        case INIT_KT:
+            doing = "getting initial credentials";
+            break;
+        case VALIDATE:
+            doing = "validating credentials";
+            break;
+        case RENEW:
+            doing = "renewing credentials";
+            break;
+        }
+
+       /* If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should
+          let the user know that maybe he/she wants -4. */
+        if (code == KRB5KRB_AP_ERR_V4_REPLY && got_k4)
+            com_err(progname, code, "while %s\n"
+                     "The KDC doesn't support v5.  "
+                     "You may want the -4 option in the future",
+                     doing);
+        else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
+            fprintf(stderr, "%s: Password incorrect while %s\n", progname,
+                    doing);
        else
-           com_err (argv[0], code, "while getting initial credentials");
-       exit(1);
+            com_err(progname, code, "while %s", doing);
+        goto cleanup;
     }
 
-    if (code = krb5_cc_initialize(kcontext, ccache, me)) {
-       com_err (argv[0], code, "when initializing cache %s",
-                cache_name?cache_name:"");
-       exit(1);
+    if (!opts->lifetime) {
+       /* We need to figure out what lifetime to use for Kerberos 4. */
+        opts->lifetime = my_creds.times.endtime - my_creds.times.authtime;
     }
 
-    if (code = krb5_cc_store_cred(kcontext, ccache, &my_creds)) {
-       com_err (argv[0], code, "while storing credentials");
-       exit(1);
+    if (code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me)) {
+        com_err(progname, code, "when initializing cache %s",
+                 opts->cache_name?opts->cache_name:"");
+        goto cleanup;
     }
 
-    if (me)
-       krb5_free_principal(kcontext, me);
+    if (code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds)) {
+        com_err(progname, code, "while storing credentials");
+        goto cleanup;
+    }
+
+    notix = 0;
+
+ cleanup:
+    if (my_creds.client == k5->me) {
+        my_creds.client = 0;
+    }
+    krb5_free_cred_contents(k5->ctx, &my_creds);
     if (keytab)
-       krb5_kt_close(kcontext, keytab);
-    if (ccache)
-       krb5_cc_close(kcontext, ccache);
-    if (addresses)
-       krb5_free_addresses(kcontext, addresses);
-
-    krb5_free_context(kcontext);
-    
-    exit(0);
+       krb5_kt_close(k5->ctx, keytab);
+    return notix?0:1;
+}
+
+int
+k4_kinit(opts, k4, password)
+    struct k_opts* opts;
+    struct k4_data* k4;
+    char* password;
+{
+    char* progname = progname_v4;
+    int k_errno = 0;
+
+    if (!got_k4)
+        return 0;
+
+    if (opts->starttime)
+        return 0;
+
+#ifdef KRB5_KRB4_COMPAT
+    if (!k4->lifetime)
+       k4->lifetime = opts->lifetime;
+    if (!k4->lifetime)
+       k4->lifetime = KRB4_BACKUP_DEFAULT_LIFE_SECS;
+
+    k4->lifetime /= (5 * 60);
+    if (k4->lifetime < 1)
+        k4->lifetime = 1;
+    if (k4->lifetime > 255)
+        k4->lifetime = 255;
+
+    switch (opts->action)
+    {
+    case INIT_PW:
+        k_errno = krb_get_pw_in_tkt(k4->aname, k4->inst, k4->realm, "krbtgt", 
+                                     k4->realm, k4->lifetime, password);
+
+        if (k_errno) {
+            fprintf(stderr, "%s: %s\n", progname, 
+                    krb_get_err_text(k_errno));
+            if (authed_k5)
+                fprintf(stderr, "Maybe your KDC does not support v4.  "
+                        "Try the -5 option next time.\n");
+            return 0;
+        }
+        return 1;
+#ifndef HAVE_KRB524
+    case INIT_KT:
+        fprintf(stderr, "%s: srvtabs are not supported\n", progname);
+        return 0;
+    case RENEW:
+        fprintf(stderr, "%s: renewal of krb4 tickets is not supported\n",
+                progname);
+        return 0;
+#endif
+    }
+#endif
+    return 0;
+}
+
+char*
+getvprogname(v)
+    char *v;
+{
+    int len = strlen(progname) + 2 + strlen(v) + 2;
+    char *ret = malloc(len);
+    if (ret)
+        sprintf(ret, "%s(v%s)", progname, v);
+    else
+        ret = progname;
+    return ret;
+}
+
+#ifdef HAVE_KRB524
+/* Convert krb5 tickets to krb4. */
+int try_convert524(k5)
+    struct k5_data* k5;
+{
+    char * progname = progname_v524;
+    krb5_error_code code = 0;
+    int icode = 0;
+    krb5_principal kpcserver = 0;
+    krb5_creds *v5creds = 0;
+    krb5_creds increds;
+    CREDENTIALS v4creds;
+
+    if (!got_k4 || !got_k5)
+       return 0;
+
+    increds.client = 0;
+
+    /* or do this directly with krb524_convert_creds_kdc */
+    krb524_init_ets(k5->ctx);
+
+    if ((code = krb5_build_principal(k5->ctx,
+                                    &kpcserver, 
+                                    krb5_princ_realm(k5->ctx, k5->me)->length,
+                                    krb5_princ_realm(k5->ctx, k5->me)->data,
+                                    "krbtgt",
+                                    krb5_princ_realm(k5->ctx, k5->me)->data,
+                                    NULL))) {
+       com_err(progname, code,
+               "while creating service principal name");
+       goto cleanup;
+    }
+
+    memset((char *) &increds, 0, sizeof(increds));
+    increds.client = k5->me;
+    increds.server = kpcserver;
+    increds.times.endtime = 0;
+    increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
+    if ((code = krb5_get_credentials(k5->ctx, 0, 
+                                    k5->cc,
+                                    &increds, 
+                                    &v5creds))) {
+       com_err(progname, code,
+               "getting V5 credentials");
+       goto cleanup;
+    }
+    if ((icode = krb524_convert_creds_kdc(k5->ctx,
+                                         v5creds,
+                                         &v4creds))) {
+       com_err(progname, icode, 
+               "converting to V4 credentials");
+       goto cleanup;
+    }
+    /* this is stolen from the v4 kinit */
+    /* initialize ticket cache */
+    if ((icode = in_tkt(v4creds.pname, v4creds.pinst)
+        != KSUCCESS)) {
+       com_err(progname, icode,
+               "trying to create the V4 ticket file");
+       goto cleanup;
+    }
+    /* stash ticket, session key, etc. for future use */
+    if ((icode = krb_save_credentials(v4creds.service,
+                                      v4creds.instance,
+                                      v4creds.realm, 
+                                      v4creds.session,
+                                      v4creds.lifetime,
+                                      v4creds.kvno,
+                                      &(v4creds.ticket_st), 
+                                      v4creds.issue_date))) {
+       com_err(progname, icode,
+               "trying to save the V4 ticket");
+       goto cleanup;
+    }
+
+ cleanup:
+    memset(&v4creds, 0, sizeof(v4creds));
+    krb5_free_creds(k5->ctx, v5creds);
+    increds.client = 0;
+    krb5_free_cred_contents(k5->ctx, &increds);
+    krb5_free_principal(k5->ctx, kpcserver);
+    return !(code || icode);
+}
+#endif /* HAVE_KRB524 */
+
+int
+main(argc, argv)
+    int argc;
+    char **argv;
+{
+    struct k_opts opts;
+    struct k5_data k5;
+    struct k4_data k4;
+    char password[255];
+
+    progname = GET_PROGNAME(argv[0]);
+    progname_v5 = getvprogname("5");
+    progname_v4 = getvprogname("4");
+    progname_v524 = getvprogname("524");
+
+    /* Ensure we can be driven from a pipe */
+    if(!isatty(fileno(stdin)))
+        setvbuf(stdin, 0, _IONBF, 0);
+    if(!isatty(fileno(stdout)))
+        setvbuf(stdout, 0, _IONBF, 0);
+    if(!isatty(fileno(stderr)))
+        setvbuf(stderr, 0, _IONBF, 0);
+
+    got_k5 = 1;
+#ifdef KRB5_KRB4_COMPAT
+    got_k4 = 1;
+#endif
+
+    memset(&opts, 0, sizeof(opts));
+    opts.action = INIT_PW;
+
+    memset(&k5, 0, sizeof(k5));
+    memset(&k4, 0, sizeof(k4));
+
+    parse_options(argc, argv, &opts);
+
+    got_k5 = k5_begin(&opts, &k5, &k4);
+    got_k4 = k4_begin(&opts, &k4);
+
+    if (opts.action == INIT_PW)
+    {
+        char prompt[255];
+        int pwsize = sizeof(password);
+        krb5_error_code code;
+
+        sprintf(prompt, "Password for %s: ", opts.principal_name);
+        password[0] = 0;
+        /*
+          Note: krb5_read_password does not actually look at the
+          context, so we're ok even if we don't have a context.  If
+          we cannot dynamically load krb5, we can substitute any
+          decent read password function instead of the krb5 one.
+        */
+        code = krb5_read_password(k5.ctx, prompt, 0, password, &pwsize);
+        if (code || pwsize == 0)
+        {
+            fprintf(stderr, "Error while reading password for '%s'\n",
+                    opts.principal_name);
+            memset(password, 0, sizeof(password));
+            exit(1);
+        }
+    }
+
+    authed_k5 = k5_kinit(&opts, &k5, password);
+    authed_k4 = k4_kinit(&opts, &k4, password);
+    memset(password, 0, sizeof(password));
+
+#ifdef HAVE_KRB524
+    if (!authed_k4 && authed_k5)
+       authed_k4 = try_convert524(&k5);
+#endif
+
+    if (authed_k5 && opts.verbose)
+        fprintf(stderr, "Authenticated to Kerberos v5\n");
+    if (authed_k4 && opts.verbose)
+        fprintf(stderr, "Authenticated to Kerberos v4\n");
+
+    k5_end(&k5);
+    k4_end(&k4);
+
+    if ((got_k5 && !authed_k5) || (got_k4 && !authed_k4))
+        exit(1);
+    return 0;
 }