2 * Copyright 2000, International Business Machines Corporation and others.
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
13 * This program acquires a Kerberos V5 ticket, or variant thereof, from
14 * the DCE KDC, using the services of an intermediary server (which happens
15 * to be implemented in the AFS/DFS translator). The intermediary takes
16 * care of most of the intricate details of the KRB5 authentication exchange,
17 * since it has available to it the appropriate KRB5 utilities. This program
18 * does have to decrypt the enciphered portion of the KDC reply to extract
19 * the session key to use with the ticket, and needs to know just enough ASN.1
20 * to decode the decrypted result. As a side-effect of using the AFS/DFS
21 * translator as the intermediary, this program also does not have to access
22 * any KRB5 location/configuration information--it just contacts the servers
23 * listed in the CellServDB in the usual manner (via ubik_.
25 * This works as follows:
27 * 1. dlog sends a GetTickets request to the intermediary.
29 * 2. The intermediary reformats the request as an KRB5 AS request(asking
30 * for a ticket made out to the specified principal, suitable for contacting
31 * the AFS/DFS translator principal. This is determined by the server, and
32 * is by default "afs".
34 * 3. Since the AS service is used directly, an appropriate ticket will
35 * be passed back immediately (there is no need to get a TGT first).
37 * 4. The translator decodes the response and, in the absense of an error,
38 * returns some in-the-clear information about the ticket, the Unix id
39 * of the user, the ticket itself, encrypted in the afs principal's key,
40 * and the session key and ticket valid times, all encrypted in a key
41 * derived from the user's password and a salt. The appropriate salt to
42 * append to the password is returned with the result. We also return
43 * a ticket type, which may indicate that the ticket is a standard
44 * Kerberos V5 ticket (RXKAD_TICKET_TYPE_KERBEROS_V5, defined in rxkad.h)
45 * or that it is in a private formats reserved by the translator (this
46 * allows the translator to strip of unecessary in-the-clear information
47 * in the ticket or even to re-encrypt the ticket in another format,
48 * if desired, to save space).
50 * 5. Finally, this program requests the user's password and attempts
51 * decryption and decoding of the session key and related information.
52 * Included in the decrypted result is a "nonce" which was supplied
53 * by the client in the first place. If the nonce is retrieved undamaged,
54 * and if we are able to decode the result (with a very limited ASN.1
55 * decoder) then it is assumed the client's password must have been correct.
57 * 6. The user id, session key, ticket, ticket type, and expiration time are
58 * all stored in the cache manager.
60 * NOTE 1: this program acquires only a simple ticket (no DCE PAC information),
61 * which is all that is required to hold a conversation with the AFS/DFS
62 * translator. The AFS/DFS translator must obtain another ticket for use with
63 * the DFS file exporter which *does* include complete user information (i.e.
64 * a PAC). That is another story.
66 * NOTE 2: no authentication libraries are provided which match this program.
67 * This program, itself, constitutes a usable authentication interface, and
68 * may be called by another program (such as login), using the -pipe switch.
71 #include <afsconfig.h>
72 #include <afs/param.h>
78 #include <sys/types.h>
96 #include <afs/com_err.h>
98 #include <afs/cellconfig.h>
103 #include <afs/afsutil.h>
106 * The password reading routine in des/readpassword.c will not work if the
107 * buffer size passed in is greater than BUFSIZ, so we pretty well have to
108 * use that constant and *HOPE* that the BUFSIZ there is the same as the
111 #define MAX_PASSWD_LEN BUFSIZ
114 * Read a null-terminated password from stdin, stop on \n or eof
119 static char gpbuf[MAX_PASSWD_LEN];
122 memset(gpbuf, 0, sizeof(gpbuf));
123 for (i = 0; i < (sizeof(gpbuf) - 1); i++) {
125 if (tc == '\n' || tc == EOF)
133 * The un-decoded version of the encrypted portion of the kdc
134 * AS reply message (see Kerberos V5 spec).
136 typedef struct kdc_as_reply {
137 des_cblock session_key;
147 static int zero_argc;
148 static char **zero_argv;
151 * Store the returned credentials as an AFS "token" for the user
152 * "AFS ID <user_id>".
155 store_afs_token(unix_id, realm_p, tkt_type, ticket_p, ticket_len, session_key,
156 starttime, endtime, set_pag)
160 unsigned char *ticket_p;
162 des_cblock session_key;
167 struct ktc_token token;
168 struct ktc_principal client, server;
170 token.startTime = starttime;
171 token.endTime = endtime;
172 memcpy((char *)&token.sessionKey, session_key, sizeof(token.sessionKey));
173 token.kvno = tkt_type;
174 token.ticketLen = ticket_len;
175 if (ticket_len > MAXKTCTICKETLEN) {
177 "dlog: DCE ticket is too long (length %d). Maximum length accepted by AFS cache manager is %d\n",
178 ticket_len, MAXKTCTICKETLEN);
181 memcpy((char *)token.ticket, (char *)ticket_p, ticket_len);
183 sprintf(client.name, "AFS ID %d", unix_id);
184 strcpy(client.instance, "");
185 strcpy(client.cell, realm_p);
187 strcpy(server.name, "afs");
188 strcpy(server.instance, "");
189 strcpy(server.cell, realm_p);
192 (&server, &token, &client, set_pag ? AFS_SETTOK_SETPAG : 0));
196 make_string(s_p, length)
200 char *new_p = (char *)malloc(length + 1);
202 fprintf(stderr, "dlog: out of memory\n");
205 memcpy(new_p, s_p, length);
206 new_p[length] = '\0';
211 * Decode an asn.1 GeneralizedTime, turning it into a 32-bit Unix time.
212 * Format is fixed at YYYYMMDDHHMMSS plus a terminating "Z".
214 * NOTE: A test for this procedure is included at the end of this file.
217 decode_asn_time(buf, buflen, utime)
222 int year, month, day, hour, mina, sec;
224 static mdays[11] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
228 || sscanf(buf, "%4d%2d%2d%2d%2d%2dZ", &year, &month, &day, &hour,
232 leapyear = month > 2 ? (year + 1) : year; /* Account for feb 29 if
233 * current year is a leap year */
234 for (days = 0, m = 0; m < month - 1; m++)
238 ((((((year - 1970) * 365 + (leapyear - 1970 + 1) / 4 + days + day -
239 1) * 24) + hour) * 60 + mina) * 60) + sec;
244 * A quick and (very) dirty ASN.1 decode of the ciphertext portion
245 * of the KDC AS reply message... good enough for a product with a short
246 * expected lifetime (this will work as long as the message format doesn't
251 * 1. The nonce is the only INTEGER with a tag of [2], at any nesting level.
252 * 2. The session key is the only OCTET STRING with length 8 and tag [1].
253 * 3. The authtime, starttime, and endtimes are the only Generalized Time
254 * strings with tags 5, 6, and 7, respectively.
255 * 4. The realm is the only General String with tag 9.
256 * 5. The tags, above, are presented in ascending order.
259 #define ASN_INTEGER 0x2
260 #define ASN_OCTET_STRING 0x4
261 #define ASN_TIME 0x18
262 #define ASN_GENERAL_STRING 0x1b
266 decode_reply(buf, buflen, reply_p)
267 unsigned char *buf; /* encoded ASN.1 string */
268 int buflen; /* length of encoded string */
269 kdc_as_reply_t *reply_p; /* result */
271 unsigned char *limit = buf + buflen;
274 char saw_kdc_rep = 0;
275 char saw_session_key = 0;
276 char saw_authtime = 0;
277 char saw_starttime = 0;
278 char saw_endtime = 0;
281 int context = -1; /* Initialize with invalid context */
283 reply_p->starttime = 0; /* This is optionally provided by kdc */
285 while (buf < limit) {
291 if (len & 0x80 && len != 0x80) {
292 unsigned int n = (len & 0x7f);
294 return 1; /* too long for us to handle */
297 len = (len << 8) | *buf++;
301 if ((op & 0x20) == 0) {
302 /* Primitive encoding */
304 if (buf + len > limit)
316 * Since non ANSI C doesn't recognize the "signed"
317 * type attribute for chars, we have to fiddle about
318 * to get sign extension (the sign bit is the top bit
319 * of the first byte).
321 #define SHIFTSIGN ((sizeof(afs_int32) - 1) * 8)
323 val = (afs_int32) (*buf++ << SHIFTSIGN) >> SHIFTSIGN;
326 val = (val << 8) | *buf++;
329 reply_p->nonce = val;
335 case ASN_OCTET_STRING:
336 if (context == 1 && len == sizeof(reply_p->session_key)) {
338 memcpy(reply_p->session_key, buf, len);
343 case ASN_GENERAL_STRING:
346 reply_p->realm = make_string(buf, len);
347 goto out; /* Best to terminate now, rather than
348 * continue--we don't know if the entire
349 * request is padded with zeroes, and if
350 * not there is a danger of misinterpreting
351 * an op-code (since the request may well
352 * be padded somewhat, for encryption purposes)
353 * This would work much better if we really
354 * tracked constructed type boundaries.
364 if (decode_asn_time(buf, len, &reply_p->authtime))
370 if (decode_asn_time(buf, len, &reply_p->starttime))
376 if (decode_asn_time(buf, len, &reply_p->endtime))
384 if ((op & 0xe0) == 0xa0) {
385 /* Remember last context label */
387 } else if ((op & 0x20) == 0) {
388 /* Skip primitive encodings we don't understand */
395 return !(saw_kdc_rep == 1 && saw_nonce == 1 && saw_session_key == 1
396 && saw_authtime == 1 && (saw_starttime == 1
397 || saw_starttime == 0)
398 && saw_endtime == 1 && saw_realm == 1);
405 struct cmd_syndesc *ts;
409 * The following signal action for AIX is necessary so that in case of a
410 * crash (i.e. core is generated) we can include the user's data section
411 * in the core dump. Unfortunately, by default, only a partial core is
412 * generated which, in many cases, isn't too useful.
414 struct sigaction nsa;
416 sigemptyset(&nsa.sa_mask);
417 nsa.sa_handler = SIG_DFL;
418 nsa.sa_flags = SA_FULLDUMP;
419 sigaction(SIGSEGV, &nsa, NULL);
424 initialize_U_error_table();
425 initialize_KTC_error_table();
426 initialize_ACFG_error_table();
428 ts = cmd_CreateSyntax(NULL, CommandProc, 0,
429 "obtain Kerberos authentication");
440 cmd_AddParm(ts, "-principal", CMD_SINGLE, CMD_OPTIONAL, "user name");
441 cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
442 cmd_AddParm(ts, "-password", CMD_SINGLE, CMD_OPTIONAL, "user's password");
443 cmd_AddParm(ts, "-servers", CMD_LIST, CMD_OPTIONAL,
444 "explicit list of servers");
445 cmd_AddParm(ts, "-lifetime", CMD_SINGLE, CMD_OPTIONAL,
446 "ticket lifetime in hh[:mm[:ss]]");
447 cmd_AddParm(ts, "-setpag", CMD_FLAG, CMD_OPTIONAL,
448 "Create a new setpag before authenticating");
449 cmd_AddParm(ts, "-pipe", CMD_FLAG, CMD_OPTIONAL,
450 "read password from stdin");
453 cmd_AddParm(ts, "-test", CMD_FLAG, CMD_OPTIONAL, "self-test");
455 code = cmd_Dispatch(argc, argv);
459 CommandProc(as, arock)
461 struct cmd_syndesc *as;
463 char name[MAXKTCNAMELEN];
464 char realm[MAXKTCREALMLEN];
466 extern ADK_GetTicket();
467 afs_int32 serverList[MAXSERVERS];
468 struct rx_connection *serverconns[MAXSERVERS];
469 struct ubik_client *ubik_handle = 0;
470 struct timeval now; /* Current time */
471 afs_int32 nonce; /* Kerberos V5 "nonce" */
472 adk_error_ptr error_p; /* Error code from ktc intermediary */
473 adk_reply_ptr reply_p; /* Reply from ktc intermediary */
474 des_cblock passwd_key; /* des key from user password */
475 des_key_schedule schedule; /* Key schedule from key */
476 kdc_as_reply_t kdcrep; /* Our own decoded version of
477 * ciphertext portion of kdc reply */
481 afs_uint32 lifetime; /* requested ticket lifetime */
482 char passwd[MAX_PASSWD_LEN];
484 static char rn[] = "dlog"; /*Routine name */
485 static int readpipe; /* reading from a pipe */
487 int explicit_cell = 0; /* servers specified explicitly */
488 int foundPassword = 0; /*Not yet, anyway */
490 struct afsconf_dir *cdir; /* Open configuration structure */
493 * Discard command line arguments, in case the password is on the
494 * command line (to avoid it showing up from a ps command).
496 for (i = 1; i < zero_argc; i++)
497 memset(zero_argv[i], 0, strlen(zero_argv[i]));
502 * Do a small self test if asked.
504 if (as->parms[aTEST].items) {
510 * Determine if we should also do a setpag based on -setpag switch.
512 dosetpag = (as->parms[aSETPAG].items ? 1 : 0);
515 * If reading the password from a pipe, don't prompt for it.
517 readpipe = (as->parms[aPIPE].items ? 1 : 0);
519 cdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
522 "dlog: unable to read or open AFS client configuration file\n");
526 if (as->parms[aCELL].items) {
527 strncpy(realm, as->parms[aCELL].items->data, sizeof(realm) - 1);
528 realm[sizeof(realm) - 1] = '\0';
531 afsconf_GetLocalCell(cdir, realm, sizeof(realm));
534 if (as->parms[aSERVERS].items) {
536 * Explicit server list. Note that if servers are specified,
537 * we don't bother trying to look up cell information, but just
538 * use the specified cell name, which must be fully specified
539 * *or* take it out of the ticket if not specified.
543 char *ap[MAXSERVERS + 2];
545 for (ip = as->parms[aSERVERS].items, i = 2; ip; ip = ip->next, i++)
549 code = ubik_ParseClientList(i, ap, serverList);
551 com_err(rn, code, "-- could not parse server list");
556 struct afsconf_cell cellinfo; /* Info for specified cell */
559 * Resolve full name of cell and get server list.
561 code = afsconf_GetCellInfo(cdir, realm, 0, &cellinfo);
563 com_err(rn, code, "-- unable to get cell info");
566 strncpy(realm, cellinfo.name, sizeof(realm) - 1);
567 realm[sizeof(realm) - 1] = '\0';
568 for (i = 0; i < cellinfo.numServers && i < MAXSERVERS; i++) {
569 serverList[i] = cellinfo.hostAddr[i].sin_addr.s_addr;
575 if (as->parms[aPRINCIPAL].items) {
576 strncpy(name, as->parms[aPRINCIPAL].items->data, sizeof(name) - 1);
577 name[sizeof(name) - 1] = '\0';
579 /* No explicit name provided: use Unix uid to get a name */
581 pw = getpwuid(getuid());
583 fprintf(stderr, "Can't determine your name from your user id.\n");
584 fprintf(stderr, "Try providing a principal name.\n");
587 strncpy(name, pw->pw_name, sizeof(name) - 1);
588 name[sizeof(name) - 1] = '\0';
591 if (as->parms[aPASSWORD].items) {
593 * Current argument is the desired password string. Remember it in
594 * our local buffer, and zero out the argument string - anyone can
595 * see it there with ps!
598 strncpy(passwd, as->parms[aPASSWORD].items->data, sizeof(passwd) - 1);
599 passwd[sizeof(passwd) - 1] = '\0';
600 memset(as->parms[aPASSWORD].items->data, 0,
601 strlen(as->parms[aPASSWORD].items->data));
604 if (as->parms[aLIFETIME].items) {
605 char *life = as->parms[aLIFETIME].items->data;
606 char *sp; /* string ptr to rest of life */
607 lifetime = 3600 * strtol(life, &sp, 0); /* hours */
610 fprintf(stderr, "%s: translating '%s' to lifetime\n", rn, life);
614 life = sp + 1; /* skip the colon */
615 lifetime += 60 * strtol(life, &sp, 0); /* minutes */
620 lifetime += strtol(life, &sp, 0); /* seconds */
633 * Make connections to all the servers.
636 for (i = 0; i < MAXSERVERS; i++) {
637 if (!serverList[i]) {
642 rx_NewConnection(serverList[i], htons(ADK_PORT), ADK_SERVICE,
643 rxnull_NewClientSecurityObject(), 0);
647 * Initialize ubik client gizmo to randomize the calls for us.
649 ubik_ClientInit(serverconns, &ubik_handle);
652 * Come up with an acceptable nonce. V5 doc says that we just need
653 * time of day. Actually, for this app, I don't think anything
654 * is really needed. Better safe than sorry (although I wonder if
655 * it might have been better to encode the AFS ID in the nonce
656 * reply field--that's the one field that the intermediate server
657 * has total control over, and which can be securely transmitted
658 * back to the client).
660 gettimeofday(&now, 0);
664 * Ask our agent to get us a Kerberos V5 ticket.
666 reply_p = (adk_reply_ptr) 0;
667 error_p = (adk_error_ptr) 0;
668 code = ubik_ADK_GetTicket(ubik_handle, 0, /* Ubik flags */
669 name, /* IN: Principal: must be exact DCE principal */
670 nonce, /* IN: Input nonce */
671 lifetime, /* IN: lifetime */
672 &error_p, /* OUT: Error, if any */
673 &reply_p); /* OUT: KTC reply, if no error */
676 * Destroy Rx connections on the off-chance this will allow less state
677 * to be preserved at the server.
679 ubik_ClientDestroy(ubik_handle);
682 * Finalize Rx. This may allow connections at the server to wind down
688 * Check for simple communication failures.
691 com_err(rn, code, "-- failed to contact authentication service");
696 * Also check for DCE errors, which are interpreted for us by
699 if (error_p && error_p->code) {
700 fprintf(stderr, "dlog: %s\n", error_p->data);
705 * Make sure the reply was filled in.
709 "dlog: unexpected error in server response; aborted\n");
714 * Get the password if it wasn't provided.
716 if (!foundPassword) {
718 strcpy(passwd, getpipepass());
720 code = des_read_pw_string(passwd, sizeof(passwd), "Password:", 0);
722 com_err(rn, code, "-- couldn't read password");
729 * Convert the password into the appropriate key block, given
730 * the salt passed back from the ADK_GetTicket call, above. Destroy
733 if (strlen(passwd) + strlen(reply_p->salt) + 1 > sizeof(passwd)) {
734 fprintf(stderr, "dlog: unexpectedly long passwd/salt combination");
737 strcat(passwd, reply_p->salt);
738 des_string_to_key(passwd, passwd_key);
739 memset(passwd, 0, strlen(passwd));
742 * Decrypt the private data returned by the DCE KDC, and forwarded
743 * to us by the translator.
745 code = des_key_sched(passwd_key, schedule);
748 des_cbc_encrypt(reply_p->private.adk_code_val,
749 reply_p->private.adk_code_val,
750 reply_p->private.adk_code_len, schedule,
751 passwd_key, DECRYPT);
754 com_err(rn, code, "-- unable to decrypt reply from the DCE KDC");
759 * Destroy the key block: it's no longer needed.
761 memset(schedule, 0, sizeof(schedule));
762 memset(passwd_key, 0, sizeof(passwd_key));
765 * Do a very quick and dirty ASN.1 decode of the relevant parts
766 * of the private data.
768 * The decrypted data contains a 12-byte header (confounder and CRC-32
769 * checksum). We choose to ignore this.
771 code = decode_reply(reply_p->private.adk_code_val + 12, /* Skip header */
772 reply_p->private.adk_code_len - 12, /* ditto */
775 if (code || kdcrep.nonce != nonce) {
777 "dlog: DCE authentication failed -- your password is probably incorrect\n");
782 * If the cell was not explicitly specified, then we hope that the local
783 * name for the cell is the same as the one in the ticket.
784 * If not, we should get an error when we store it, so the user will see
785 * the errant name at that time.
788 strcpy(realm, kdcrep.realm);
791 * Make an AFS token out of the ticket and session key, and install it
792 * in the cache manager.
795 store_afs_token(reply_p->unix_id, realm, reply_p->tktype,
796 reply_p->ticket.adk_code_val,
797 reply_p->ticket.adk_code_len, kdcrep.session_key,
798 kdcrep.starttime ? kdcrep.starttime : kdcrep.authtime,
799 kdcrep.endtime, dosetpag);
802 com_err("dlog", code, "-- failed to store tickets");
811 * Check the ASN.1 generalized time conversion routine, which assumes
812 * the restricted format defined in the Kerberos V5 document.
816 * The times in this array were checked independently with the following perl
819 * ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(shift);
820 * $mon++; $year += 1900;
821 * printf("%04d%02d%02d%02d%02d%02dZ\n", $year, $mon, $mday, $hour, $min, $sec);
824 char *generalized_time_p;
828 "19700101000000Z", 0}, {
829 "19930101000000Z", 725846400}, {
830 "19940101000000Z", 757382400}, {
831 "19940201000000Z", 760060800}, {
832 "19940301000000Z", 762480000}, {
833 "19940401000000Z", 765158400}, {
834 "19950101000000Z", 788918400}, {
835 "19950201000000Z", 791596800}, {
836 "19950301000000Z", 794016000}, {
837 "19950401000000Z", 796694400}, {
838 "19950501000000Z", 799286400}, {
839 "19950601000000Z", 801964800}, {
840 "19950701000000Z", 804556800}, {
841 "19950801000000Z", 807235200}, {
842 "19950901000000Z", 809913600}, {
843 "19951001000000Z", 812505600}, {
844 "19951101000000Z", 815184000}, {
845 "19951201000000Z", 817776000}, {
846 "19951231235959Z", 820454399}, {
847 "19960101000000Z", 820454400}, {
848 "19960131000000Z", 823046400}, {
849 "19960131235959Z", 823132799}, {
850 "19960201000000Z", 823132800}, {
851 "19960229000000Z", 825552000}, {
852 "19960229235959Z", 825638399}, {
853 "19960301000000Z", 825638400}, {
854 "19960331000000Z", 828230400}, {
855 "19960331235959Z", 828316799}, {
856 "19970101000000Z", 852076800}, {
857 "19980101000000Z", 883612800}, {
858 "19990101000000Z", 915148800}, {
859 "20000101000000Z", 946684800}, {
860 "20010101000000Z", 978307200}, {
861 "20020101000000Z", 1009843200}, {
862 "20030101000000Z", 1041379200}, {
863 "20040101000000Z", 1072915200}, {
864 "20050101000000Z", 1104537600}, {
865 "20380119031407Z", 2147483647},};
872 for (i = 0; i < sizeof(test_times) / sizeof(test_times[0]); i++) {
873 struct test_times *t_p = &test_times[i];
878 decode_asn_time(t_p->generalized_time_p,
879 strlen(t_p->generalized_time_p), &unix_time);
881 printf("dlog: decode of ASN.1 time %s failed\n",
882 t_p->generalized_time_p);
884 } else if (t_p->unix_time != unix_time) {
886 ("dlog: ASN.1 time %s converted incorrectly to %lu (should be %lu)\n",
887 t_p->generalized_time_p, unix_time, t_p->unix_time);
892 fprintf(stderr, "dlog: self test failed\n");
895 fprintf(stderr, "dlog: self test OK\n");