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_Call).
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 <afs/param.h>
72 #include <afsconfig.h>
77 #include <sys/types.h>
87 #include <afs/com_err.h>
89 #include <afs/cellconfig.h>
94 #include <afs/afsutil.h>
97 * The password reading routine in des/readpassword.c will not work if the
98 * buffer size passed in is greater than BUFSIZ, so we pretty well have to
99 * use that constant and *HOPE* that the BUFSIZ there is the same as the
102 #define MAX_PASSWD_LEN BUFSIZ
105 * Read a null-terminated password from stdin, stop on \n or eof
107 static char *getpipepass() {
108 static char gpbuf[MAX_PASSWD_LEN];
111 bzero(gpbuf, sizeof(gpbuf));
112 for(i=0; i<(sizeof(gpbuf)-1); i++) {
114 if (tc == '\n' || tc == EOF) break;
121 * The un-decoded version of the encrypted portion of the kdc
122 * AS reply message (see Kerberos V5 spec).
124 typedef struct kdc_as_reply {
125 des_cblock session_key;
135 static int zero_argc;
136 static char **zero_argv;
139 * Store the returned credentials as an AFS "token" for the user
140 * "AFS ID <user_id>".
142 int store_afs_token(unix_id, realm_p, tkt_type, ticket_p, ticket_len,
143 session_key, starttime, endtime, set_pag)
147 unsigned char *ticket_p;
149 des_cblock session_key;
154 struct ktc_token token;
155 struct ktc_principal client, server;
157 token.startTime = starttime;
158 token.endTime = endtime;
159 bcopy(session_key, (char *) &token.sessionKey, sizeof(token.sessionKey));
160 token.kvno = tkt_type;
161 token.ticketLen = ticket_len;
162 if (ticket_len > MAXKTCTICKETLEN) {
163 fprintf(stderr, "dlog: DCE ticket is too long (length %d). Maximum length accepted by AFS cache manager is %d\n", MAXKTCTICKETLEN);
166 bcopy((char *) ticket_p, (char *) token.ticket, ticket_len);
168 sprintf(client.name, "AFS ID %d", unix_id);
169 strcpy(client.instance, "");
170 strcpy(client.cell, realm_p);
172 strcpy(server.name, "afs");
173 strcpy(server.instance, "");
174 strcpy(server.cell, realm_p);
176 return (ktc_SetToken(&server, &token, &client,
177 set_pag ? AFS_SETTOK_SETPAG : 0));
180 char *make_string(s_p, length)
184 char *new_p = (char *) malloc(length + 1);
185 if (new_p == (char *) 0) {
186 fprintf(stderr, "dlog: out of memory\n");
189 bcopy(s_p, new_p, length);
190 new_p[length] = '\0';
195 * Decode an asn.1 GeneralizedTime, turning it into a 32-bit Unix time.
196 * Format is fixed at YYYYMMDDHHMMSS plus a terminating "Z".
198 * NOTE: A test for this procedure is included at the end of this file.
200 int decode_asn_time(buf, buflen, utime)
205 int year, month, day, hour, mina, sec;
207 static mdays[11] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30};
211 sscanf(buf, "%4d%2d%2d%2d%2d%2dZ",
212 &year, &month, &day, &hour, &mina, &sec) != 6) {
215 leapyear = month > 2 ? (year + 1) : year; /* Account for feb 29 if
216 current year is a leap year */
217 for (days = 0, m = 0; m < month - 1; m++) days += mdays[m];
220 ((((((year - 1970) * 365 + (leapyear - 1970 + 1)/4
221 + days + day - 1) * 24) + hour) * 60 + mina) * 60) + sec;
226 * A quick and (very) dirty ASN.1 decode of the ciphertext portion
227 * of the KDC AS reply message... good enough for a product with a short
228 * expected lifetime (this will work as long as the message format doesn't
233 * 1. The nonce is the only INTEGER with a tag of [2], at any nesting level.
234 * 2. The session key is the only OCTET STRING with length 8 and tag [1].
235 * 3. The authtime, starttime, and endtimes are the only Generalized Time
236 * strings with tags 5, 6, and 7, respectively.
237 * 4. The realm is the only General String with tag 9.
238 * 5. The tags, above, are presented in ascending order.
241 #define ASN_INTEGER 0x2
242 #define ASN_OCTET_STRING 0x4
243 #define ASN_TIME 0x18
244 #define ASN_GENERAL_STRING 0x1b
247 int decode_reply(buf, buflen, reply_p)
248 unsigned char *buf; /* encoded ASN.1 string */
249 int buflen; /* length of encoded string */
250 kdc_as_reply_t *reply_p; /* result */
252 unsigned char *limit = buf + buflen;
255 char saw_kdc_rep = 0;
256 char saw_session_key = 0;
257 char saw_authtime = 0;
258 char saw_starttime = 0;
259 char saw_endtime = 0;
262 int context = -1; /* Initialize with invalid context */
264 reply_p->starttime = 0; /* This is optionally provided by kdc */
266 while (buf < limit) {
272 if (len & 0x80 && len != 0x80) {
273 unsigned int n = (len & 0x7f);
275 return 1; /* too long for us to handle */
278 len = (len << 8) | *buf++;
282 if ((op & 0x20) == 0) {
283 /* Primitive encoding */
285 if (buf + len > limit)
297 * Since non ANSI C doesn't recognize the "signed"
298 * type attribute for chars, we have to fiddle about
299 * to get sign extension (the sign bit is the top bit
300 * of the first byte).
302 #define SHIFTSIGN ((sizeof(afs_int32) - 1) * 8)
304 val = (afs_int32) (*buf++ << SHIFTSIGN) >> SHIFTSIGN;
307 val = (val << 8) | *buf++;
310 reply_p->nonce = val;
316 case ASN_OCTET_STRING:
317 if (context == 1 && len == sizeof(reply_p->session_key)) {
319 bcopy(buf, reply_p->session_key, len);
324 case ASN_GENERAL_STRING:
327 reply_p->realm = make_string(buf, len);
328 goto out; /* Best to terminate now, rather than
329 * continue--we don't know if the entire
330 * request is padded with zeroes, and if
331 * not there is a danger of misinterpreting
332 * an op-code (since the request may well
333 * be padded somewhat, for encryption purposes)
334 * This would work much better if we really
335 * tracked constructed type boundaries.
345 if (decode_asn_time(buf, len, &reply_p->authtime))
351 if (decode_asn_time(buf, len, &reply_p->starttime))
357 if (decode_asn_time(buf, len, &reply_p->endtime))
365 if ((op & 0xe0) == 0xa0) {
366 /* Remember last context label */
368 } else if ((op & 0x20) == 0) {
369 /* Skip primitive encodings we don't understand */
376 return ! (saw_kdc_rep == 1 && saw_nonce == 1 && saw_session_key == 1 &&
377 saw_authtime == 1 && (saw_starttime == 1 || saw_starttime == 0) &&
378 saw_endtime == 1 && saw_realm == 1);
384 { struct cmd_syndesc *ts;
388 * The following signal action for AIX is necessary so that in case of a
389 * crash (i.e. core is generated) we can include the user's data section
390 * in the core dump. Unfortunately, by default, only a partial core is
391 * generated which, in many cases, isn't too useful.
393 struct sigaction nsa;
395 sigemptyset(&nsa.sa_mask);
396 nsa.sa_handler = SIG_DFL;
397 nsa.sa_flags = SA_FULLDUMP;
398 sigaction(SIGSEGV, &nsa, NULL);
403 initialize_u_error_table();
404 initialize_ktc_error_table();
405 initialize_acfg_error_table();
407 ts = cmd_CreateSyntax((char *) 0, CommandProc, 0, "obtain Kerberos authentication");
418 cmd_AddParm(ts, "-principal", CMD_SINGLE, CMD_OPTIONAL, "user name");
419 cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
420 cmd_AddParm(ts, "-password", CMD_SINGLE, CMD_OPTIONAL, "user's password");
421 cmd_AddParm(ts, "-servers", CMD_LIST, CMD_OPTIONAL, "explicit list of servers");
422 cmd_AddParm(ts, "-lifetime", CMD_SINGLE, CMD_OPTIONAL, "ticket lifetime in hh[:mm[:ss]]");
423 cmd_AddParm(ts, "-setpag", CMD_FLAG, CMD_OPTIONAL, "Create a new setpag before authenticating");
424 cmd_AddParm(ts, "-pipe", CMD_FLAG, CMD_OPTIONAL, "read password from stdin");
427 cmd_AddParm(ts, "-test", CMD_FLAG, CMD_OPTIONAL, "self-test");
429 code = cmd_Dispatch(argc, argv);
433 CommandProc (as, arock)
435 struct cmd_syndesc *as;
437 char name[MAXKTCNAMELEN];
438 char realm[MAXKTCREALMLEN];
440 extern ADK_GetTicket();
441 afs_int32 serverList[MAXSERVERS];
442 struct rx_connection *serverconns[MAXSERVERS];
443 struct ubik_client *ubik_handle=0;
444 struct timeval now; /* Current time */
445 afs_int32 nonce; /* Kerberos V5 "nonce" */
446 adk_error_ptr error_p; /* Error code from ktc intermediary */
447 adk_reply_ptr reply_p; /* Reply from ktc intermediary */
448 des_cblock passwd_key; /* des key from user password */
449 des_key_schedule schedule; /* Key schedule from key */
450 kdc_as_reply_t kdcrep; /* Our own decoded version of
451 ciphertext portion of kdc reply */
455 afs_uint32 lifetime; /* requested ticket lifetime */
456 char passwd[MAX_PASSWD_LEN];
458 static char rn[] = "dlog"; /*Routine name*/
459 static int readpipe; /* reading from a pipe */
461 int explicit_cell = 0; /* servers specified explicitly */
462 int foundPassword = 0; /*Not yet, anyway*/
464 struct afsconf_dir *cdir; /* Open configuration structure */
467 * Discard command line arguments, in case the password is on the
468 * command line (to avoid it showing up from a ps command).
470 for (i=1; i<zero_argc; i++) bzero (zero_argv[i], strlen(zero_argv[i]));
475 * Do a small self test if asked.
477 if (as->parms[aTEST].items) {
483 * Determine if we should also do a setpag based on -setpag switch.
485 dosetpag = (as->parms[aSETPAG].items ? 1 : 0);
488 * If reading the password from a pipe, don't prompt for it.
490 readpipe = (as->parms[aPIPE].items ? 1 : 0);
492 cdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
494 fprintf(stderr, "dlog: unable to read or open AFS client configuration file\n");
498 if (as->parms[aCELL].items) {
499 strncpy (realm, as->parms[aCELL].items->data, sizeof(realm) - 1);
500 realm[sizeof(realm) - 1] = '\0';
503 afsconf_GetLocalCell(cdir, realm, sizeof(realm));
506 if (as->parms[aSERVERS].items) {
508 * Explicit server list. Note that if servers are specified,
509 * we don't bother trying to look up cell information, but just
510 * use the specified cell name, which must be fully specified
511 * *or* take it out of the ticket if not specified.
515 char *ap[MAXSERVERS+2];
517 for (ip = as->parms[aSERVERS].items, i=2; ip; ip=ip->next, i++)
521 code = ubik_ParseClientList(i, ap, serverList);
523 com_err (rn, code, "-- could not parse server list");
528 struct afsconf_cell cellinfo; /* Info for specified cell */
531 * Resolve full name of cell and get server list.
533 code = afsconf_GetCellInfo(cdir, realm, 0, &cellinfo);
535 com_err(rn, code, "-- unable to get cell info");
538 strncpy(realm, cellinfo.name, sizeof(realm) - 1);
539 realm[sizeof(realm) - 1] = '\0';
540 for (i = 0; i < cellinfo.numServers && i < MAXSERVERS; i++) {
541 serverList[i] = cellinfo.hostAddr[i].sin_addr.s_addr;
543 if (i < MAXSERVERS) serverList[i] = 0;
546 if (as->parms[aPRINCIPAL].items) {
547 strncpy(name, as->parms[aPRINCIPAL].items->data, sizeof(name) - 1);
548 name[sizeof(name) - 1] = '\0';
550 /* No explicit name provided: use Unix uid to get a name */
552 pw = getpwuid(getuid());
554 fprintf (stderr, "Can't determine your name from your user id.\n");
555 fprintf (stderr, "Try providing a principal name.\n");
558 strncpy(name, pw->pw_name, sizeof(name) - 1);
559 name[sizeof(name) - 1] = '\0';
562 if (as->parms[aPASSWORD].items) {
564 * Current argument is the desired password string. Remember it in
565 * our local buffer, and zero out the argument string - anyone can
566 * see it there with ps!
569 strncpy (passwd, as->parms[aPASSWORD].items->data, sizeof(passwd) - 1);
570 passwd[sizeof(passwd) - 1] = '\0';
571 bzero (as->parms[aPASSWORD].items->data,
572 strlen(as->parms[aPASSWORD].items->data));
575 if (as->parms[aLIFETIME].items) {
576 char *life = as->parms[aLIFETIME].items->data;
577 char *sp; /* string ptr to rest of life */
578 lifetime = 3600*strtol (life, &sp, 0); /* hours */
581 fprintf (stderr, "%s: translating '%s' to lifetime\n",
586 life = sp+1; /* skip the colon */
587 lifetime += 60*strtol (life, &sp, 0); /* minutes */
588 if (sp == life) goto bad_lifetime;
591 lifetime += strtol (life, &sp, 0); /* seconds */
592 if (sp == life) goto bad_lifetime;
593 if (*sp) goto bad_lifetime;
594 } else if (*sp) goto bad_lifetime;
595 } else if (*sp) goto bad_lifetime;
599 * Make connections to all the servers.
602 for (i = 0; i < MAXSERVERS; i++) {
603 if (! serverList[i]) {
607 serverconns[i] = rx_NewConnection
608 (serverList[i], htons(ADK_PORT), ADK_SERVICE,
609 rxnull_NewClientSecurityObject(), 0);
613 * Initialize ubik client gizmo to randomize the calls for us.
615 ubik_ClientInit(serverconns, &ubik_handle);
618 * Come up with an acceptable nonce. V5 doc says that we just need
619 * time of day. Actually, for this app, I don't think anything
620 * is really needed. Better safe than sorry (although I wonder if
621 * it might have been better to encode the AFS ID in the nonce
622 * reply field--that's the one field that the intermediate server
623 * has total control over, and which can be securely transmitted
624 * back to the client).
626 gettimeofday(&now, 0);
630 * Ask our agent to get us a Kerberos V5 ticket.
632 reply_p = (adk_reply_ptr) 0;
633 error_p = (adk_error_ptr) 0;
638 name, /* IN: Principal: must be exact DCE principal */
639 nonce, /* IN: Input nonce */
640 lifetime, /* IN: lifetime */
641 &error_p, /* OUT: Error, if any */
642 &reply_p); /* OUT: KTC reply, if no error */
645 * Destroy Rx connections on the off-chance this will allow less state
646 * to be preserved at the server.
648 ubik_ClientDestroy(ubik_handle);
651 * Finalize Rx. This may allow connections at the server to wind down
657 * Check for simple communication failures.
660 com_err(rn, code, "-- failed to contact authentication service");
665 * Also check for DCE errors, which are interpreted for us by
668 if (error_p && error_p->code) {
669 fprintf(stderr, "dlog: %s\n", error_p->data);
674 * Make sure the reply was filled in.
677 fprintf(stderr, "dlog: unexpected error in server response; aborted\n");
682 * Get the password if it wasn't provided.
684 if (!foundPassword) {
686 strcpy(passwd, getpipepass());
688 code = des_read_pw_string(passwd, sizeof(passwd), "Password:", 0);
690 com_err (rn, code, "-- couldn't read password");
697 * Convert the password into the appropriate key block, given
698 * the salt passed back from the ADK_GetTicket call, above. Destroy
701 if (strlen(passwd) + strlen(reply_p->salt) + 1 > sizeof(passwd)) {
702 fprintf(stderr, "dlog: unexpectedly long passwd/salt combination");
705 strcat(passwd, reply_p->salt);
706 des_string_to_key(passwd, passwd_key);
707 bzero(passwd, strlen(passwd));
710 * Decrypt the private data returned by the DCE KDC, and forwarded
711 * to us by the translator.
713 code = des_key_sched (passwd_key, schedule);
715 code = des_cbc_encrypt
716 (reply_p->private.adk_code_val, reply_p->private.adk_code_val,
717 reply_p->private.adk_code_len, schedule, passwd_key, DECRYPT);
720 com_err(rn, code, "-- unable to decrypt reply from the DCE KDC");
725 * Destroy the key block: it's no longer needed.
727 bzero(schedule, sizeof(schedule));
728 bzero(passwd_key, sizeof(passwd_key));
731 * Do a very quick and dirty ASN.1 decode of the relevant parts
732 * of the private data.
734 * The decrypted data contains a 12-byte header (confounder and CRC-32
735 * checksum). We choose to ignore this.
737 code = decode_reply(reply_p->private.adk_code_val + 12, /* Skip header */
738 reply_p->private.adk_code_len - 12, /* ditto */
741 if (code || kdcrep.nonce != nonce) {
742 fprintf(stderr, "dlog: DCE authentication failed -- your password is probably incorrect\n");
747 * If the cell was not explicitly specified, then we hope that the local
748 * name for the cell is the same as the one in the ticket.
749 * If not, we should get an error when we store it, so the user will see
750 * the errant name at that time.
752 if (!explicit_cell) strcpy(realm, kdcrep.realm);
755 * Make an AFS token out of the ticket and session key, and install it
756 * in the cache manager.
758 code = store_afs_token(reply_p->unix_id,
761 reply_p->ticket.adk_code_val,
762 reply_p->ticket.adk_code_len,
764 kdcrep.starttime? kdcrep.starttime : kdcrep.authtime,
769 com_err("dlog", code, "-- failed to store tickets");
778 * Check the ASN.1 generalized time conversion routine, which assumes
779 * the restricted format defined in the Kerberos V5 document.
783 * The times in this array were checked independently with the following perl
786 * ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(shift);
787 * $mon++; $year += 1900;
788 * printf("%04d%02d%02d%02d%02d%02dZ\n", $year, $mon, $mday, $hour, $min, $sec);
791 char *generalized_time_p;
794 {"19700101000000Z", 0},
795 {"19930101000000Z", 725846400},
796 {"19940101000000Z", 757382400},
797 {"19940201000000Z", 760060800},
798 {"19940301000000Z", 762480000},
799 {"19940401000000Z", 765158400},
800 {"19950101000000Z", 788918400},
801 {"19950201000000Z", 791596800},
802 {"19950301000000Z", 794016000},
803 {"19950401000000Z", 796694400},
804 {"19950501000000Z", 799286400},
805 {"19950601000000Z", 801964800},
806 {"19950701000000Z", 804556800},
807 {"19950801000000Z", 807235200},
808 {"19950901000000Z", 809913600},
809 {"19951001000000Z", 812505600},
810 {"19951101000000Z", 815184000},
811 {"19951201000000Z", 817776000},
812 {"19951231235959Z", 820454399},
813 {"19960101000000Z", 820454400},
814 {"19960131000000Z", 823046400},
815 {"19960131235959Z", 823132799},
816 {"19960201000000Z", 823132800},
817 {"19960229000000Z", 825552000},
818 {"19960229235959Z", 825638399},
819 {"19960301000000Z", 825638400},
820 {"19960331000000Z", 828230400},
821 {"19960331235959Z", 828316799},
822 {"19970101000000Z", 852076800},
823 {"19980101000000Z", 883612800},
824 {"19990101000000Z", 915148800},
825 {"20000101000000Z", 946684800},
826 {"20010101000000Z", 978307200},
827 {"20020101000000Z", 1009843200},
828 {"20030101000000Z", 1041379200},
829 {"20040101000000Z", 1072915200},
830 {"20050101000000Z", 1104537600},
831 {"20380119031407Z", 2147483647},
838 for (i = 0; i < sizeof(test_times) / sizeof(test_times[0]); i++) {
839 struct test_times *t_p = &test_times[i];
843 status = decode_asn_time
844 (t_p->generalized_time_p, strlen(t_p->generalized_time_p), &unix_time);
846 printf("dlog: decode of ASN.1 time %s failed\n",
847 t_p->generalized_time_p);
849 } else if (t_p->unix_time != unix_time) {
850 printf("dlog: ASN.1 time %s converted incorrectly to %lu (should be %lu)\n",
851 t_p->generalized_time_p, unix_time, t_p->unix_time);
856 fprintf(stderr, "dlog: self test failed\n");
859 fprintf(stderr, "dlog: self test OK\n");