convert-from-bsd-to-posix-string-and-memory-functions-20010807
[openafs.git] / src / dauth / dlog.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
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
8  */
9
10 /*
11  * dlog
12  *
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).
24  *
25  * This works as follows:
26  * 
27  * 1. dlog sends a GetTickets request to the intermediary.
28  *
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".
33  *
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).
36  *
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).
49  *
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.
56  *
57  * 6. The user id, session key, ticket, ticket type, and expiration time are
58  *    all stored in the cache manager.
59  *
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.
65  *
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.
69  */
70
71 #include <afsconfig.h>
72 #include <afs/param.h>
73
74 RCSID("$Header$");
75
76 #include <afs/stds.h>
77 #include <sys/types.h>
78 #include <rx/xdr.h>
79 #ifdef  AFS_AIX32_ENV
80 #include <signal.h>
81 #endif
82
83 #include <ubik.h>
84
85 #include <stdio.h>
86 #include <pwd.h>
87 #include <afs/com_err.h>
88 #include <afs/auth.h>
89 #include <afs/cellconfig.h>
90 #include <afs/cmd.h>
91 #include "adkint.h"
92 #include "assert.h"
93 #include <des.h>
94 #include <afs/afsutil.h>
95
96 /*
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
100  * BUFSIZ here.
101  */
102 #define MAX_PASSWD_LEN BUFSIZ
103
104 /*
105  * Read a null-terminated password from stdin, stop on \n or eof
106  */
107 static char *getpipepass() {
108     static char gpbuf[MAX_PASSWD_LEN];
109
110     register int i, tc;
111     memset(gpbuf, 0, sizeof(gpbuf));
112     for(i=0; i<(sizeof(gpbuf)-1); i++) {
113         tc = fgetc(stdin);
114         if (tc == '\n' || tc == EOF) break;
115         gpbuf[i] = tc;
116     }
117     return gpbuf;
118 }
119
120 /*
121  * The un-decoded version of the encrypted portion of the kdc
122  * AS reply message (see Kerberos V5 spec).
123  */
124 typedef struct kdc_as_reply {
125     des_cblock session_key;
126     afs_int32 nonce;
127     afs_int32 authtime;
128     afs_int32 starttime;
129     afs_int32 endtime;
130     char *realm;
131 } kdc_as_reply_t;
132
133 int CommandProc();
134
135 static int zero_argc;
136 static char **zero_argv;
137
138 /*
139  * Store the returned credentials as an AFS "token" for the user
140  * "AFS ID <user_id>".
141  */
142 int store_afs_token(unix_id, realm_p, tkt_type, ticket_p, ticket_len,
143                     session_key, starttime, endtime, set_pag)
144     afs_int32 unix_id;
145     char *realm_p;
146     afs_int32 tkt_type;
147     unsigned char *ticket_p;
148     int ticket_len;
149     des_cblock session_key;
150     afs_int32 starttime;
151     afs_int32 endtime;
152     int set_pag;
153 {
154     struct ktc_token token;
155     struct ktc_principal client, server;
156
157     token.startTime = starttime;
158     token.endTime = endtime;
159     memcpy((char *) &token.sessionKey, session_key, 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);
164         exit(1);
165     }
166     memcpy((char *) token.ticket, (char *) ticket_p, ticket_len);
167
168     sprintf(client.name, "AFS ID %d", unix_id);
169     strcpy(client.instance, "");
170     strcpy(client.cell, realm_p);
171
172     strcpy(server.name, "afs");
173     strcpy(server.instance, "");
174     strcpy(server.cell, realm_p);
175
176     return (ktc_SetToken(&server, &token, &client,
177                          set_pag ? AFS_SETTOK_SETPAG : 0));
178 }
179
180 char *make_string(s_p, length)
181     char *s_p;
182     int length;
183 {
184     char *new_p = (char *) malloc(length + 1);
185     if (new_p == (char *) 0) {
186         fprintf(stderr, "dlog: out of memory\n");
187         exit(1);
188     }
189     memcpy(new_p, s_p, length);
190     new_p[length] = '\0';
191     return new_p;
192 }
193
194 /*
195  * Decode an asn.1 GeneralizedTime, turning it into a 32-bit Unix time.
196  * Format is fixed at YYYYMMDDHHMMSS plus a terminating "Z".
197  *
198  * NOTE: A test for this procedure is included at the end of this file.
199  */
200 int decode_asn_time(buf, buflen, utime)
201     char *buf;
202     int buflen;
203     afs_int32 *utime;
204 {
205     int year, month, day, hour, mina, sec;
206     int leapyear, days;
207     static mdays[11] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30};
208     int m;
209
210     if (buflen != 15 ||
211         sscanf(buf, "%4d%2d%2d%2d%2d%2dZ",
212                &year, &month, &day, &hour, &mina, &sec) != 6) {
213         return 1;
214     }
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];
218
219     *utime =
220         ((((((year - 1970) * 365 + (leapyear - 1970 + 1)/4
221              + days + day - 1) * 24) + hour) * 60 + mina) * 60) + sec;
222     return 0;
223 }
224
225 /*
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
229  * change much).
230  *
231  * Assumptions:
232  * 
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.
239  */
240
241 #define ASN_INTEGER             0x2
242 #define ASN_OCTET_STRING        0x4
243 #define ASN_TIME                0x18
244 #define ASN_GENERAL_STRING      0x1b
245 #define KDC_REP                 0x7a
246
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 */
251 {
252     unsigned char *limit = buf + buflen;
253
254     char saw_nonce = 0;
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;
260     char saw_realm = 0;
261
262     int context = -1;           /* Initialize with invalid context */
263     
264     reply_p->starttime = 0;     /* This is optionally provided by kdc */
265
266     while (buf < limit) {
267         int op;
268         unsigned int len;
269
270         op = *buf++;
271         len = *buf++;
272         if (len & 0x80 && len != 0x80) {
273             unsigned int n = (len & 0x7f);
274             if (n > sizeof(len))
275                 return 1;       /* too long for us to handle */
276             len = 0;
277             while (n) {
278                 len = (len << 8) | *buf++;
279                 n--;
280             }
281         }
282         if ((op & 0x20) == 0) {
283             /* Primitive encoding */
284             /* Bounds check */
285             if (buf + len > limit)
286                 return 1;
287         }
288
289         switch (op) {
290           case KDC_REP:
291             saw_kdc_rep++;
292             break;
293
294           case ASN_INTEGER:
295             {
296                 /*
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).
301                  */
302 #define         SHIFTSIGN ((sizeof(afs_int32) - 1) * 8)
303                 afs_int32 val;
304                 val = (afs_int32) (*buf++ << SHIFTSIGN) >> SHIFTSIGN;
305
306                 while (--len)
307                     val = (val << 8) | *buf++;
308
309                 if (context == 2) {
310                     reply_p->nonce = val;
311                     saw_nonce++;
312                 }
313             }
314             break;
315
316           case ASN_OCTET_STRING:
317             if (context == 1 && len == sizeof(reply_p->session_key)) {
318                 saw_session_key++;
319                 memcpy(reply_p->session_key, buf, len);
320             }
321             buf += len;
322             break;
323
324           case ASN_GENERAL_STRING:
325             if (context == 9) {
326                 saw_realm = 1;
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.
336                                  */
337             }
338             buf += len;
339             break;
340
341           case ASN_TIME:
342             switch (context) {
343               case 5:
344                 saw_authtime++;
345                 if (decode_asn_time(buf, len, &reply_p->authtime)) 
346                     return 1;
347                 break;
348
349               case 6:
350                 saw_starttime++;
351                 if (decode_asn_time(buf, len, &reply_p->starttime))
352                     return 1;
353                 break;
354                 
355               case 7:
356                 saw_endtime++;
357                 if (decode_asn_time(buf, len, &reply_p->endtime))
358                     return 1;
359                 break;
360             }
361             buf += len;
362             break;
363
364           default:
365             if ((op & 0xe0) == 0xa0) {
366                 /* Remember last context label */
367                 context = op & 0x1f;
368             } else if ((op & 0x20) == 0) {
369                 /* Skip primitive encodings we don't understand */
370                 buf += len;
371             }
372         }
373     }
374
375   out:
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);
379 }
380
381 main (argc, argv)
382   int   argc;
383   char *argv[];
384 {   struct cmd_syndesc *ts;
385     afs_int32 code;
386 #ifdef  AFS_AIX32_ENV
387     /*
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.
392      */
393     struct sigaction nsa;
394     
395     sigemptyset(&nsa.sa_mask);
396     nsa.sa_handler = SIG_DFL;
397     nsa.sa_flags = SA_FULLDUMP;
398     sigaction(SIGSEGV, &nsa, NULL);
399 #endif
400     zero_argc = argc;
401     zero_argv = argv;
402
403     initialize_u_error_table();
404     initialize_ktc_error_table();
405     initialize_acfg_error_table();
406
407     ts = cmd_CreateSyntax((char *) 0, CommandProc, 0, "obtain Kerberos authentication");
408
409 #define aPRINCIPAL 0
410 #define aCELL 1
411 #define aPASSWORD 2
412 #define aSERVERS 3
413 #define aLIFETIME 4
414 #define aSETPAG 5
415 #define aPIPE 6
416 #define aTEST 7
417
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");
425
426 #ifdef DLOG_TEST
427     cmd_AddParm(ts, "-test", CMD_FLAG, CMD_OPTIONAL, "self-test");
428 #endif
429     code = cmd_Dispatch(argc, argv);
430     exit(code);
431 }
432
433 CommandProc (as, arock)
434   char *arock;
435   struct cmd_syndesc *as;
436 {
437     char  name[MAXKTCNAMELEN];
438     char  realm[MAXKTCREALMLEN];
439
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 */
452
453     int   code;
454     int   i, dosetpag;
455     afs_uint32 lifetime;                /* requested ticket lifetime */
456     char passwd[MAX_PASSWD_LEN];
457
458     static char rn[] = "dlog";          /*Routine name*/
459     static int readpipe;                /* reading from a pipe */
460
461     int explicit_cell = 0;              /* servers specified explicitly */
462     int foundPassword = 0;              /*Not yet, anyway*/
463
464     struct afsconf_dir *cdir;           /* Open configuration structure */
465
466     /*
467      * Discard command line arguments, in case the password is on the
468      * command line (to avoid it showing up from a ps command).
469      */
470     for (i=1; i<zero_argc; i++) memset(zero_argv[i], 0, strlen(zero_argv[i]));
471     zero_argc = 0;
472
473 #ifdef DLOG_TEST
474     /*;
475      * Do a small self test if asked.
476      */
477     if (as->parms[aTEST].items) {
478         exit(self_test());
479     }
480 #endif
481
482     /*
483      * Determine if we should also do a setpag based on -setpag switch.
484      */
485     dosetpag = (as->parms[aSETPAG].items ? 1 : 0);
486
487     /*
488      * If reading the password from a pipe, don't prompt for it.
489      */  
490     readpipe = (as->parms[aPIPE].items ? 1 : 0);
491
492     cdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
493     if (!cdir) {
494         fprintf(stderr, "dlog: unable to read or open AFS client configuration file\n");
495         exit(1);
496     }
497
498     if (as->parms[aCELL].items) {
499         strncpy (realm, as->parms[aCELL].items->data, sizeof(realm) - 1);
500         realm[sizeof(realm) - 1] = '\0';
501         explicit_cell = 1;
502     } else {
503         afsconf_GetLocalCell(cdir, realm, sizeof(realm));
504     }
505
506     if (as->parms[aSERVERS].items) {
507         /*
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.
512          */
513         int i;
514         struct cmd_item *ip;
515         char *ap[MAXSERVERS+2];
516
517         for (ip = as->parms[aSERVERS].items, i=2; ip; ip=ip->next, i++)
518             ap[i] = ip->data;
519         ap[0] = "";
520         ap[1] = "-servers";
521         code = ubik_ParseClientList(i, ap, serverList);
522         if (code) {
523             com_err (rn, code, "-- could not parse server list");
524             exit(1);
525         }
526     } else {
527         int i;
528         struct afsconf_cell cellinfo;   /* Info for specified cell */
529
530         /*
531          * Resolve full name of cell and get server list.
532          */
533         code = afsconf_GetCellInfo(cdir, realm, 0, &cellinfo);
534         if (code) {
535             com_err(rn, code, "-- unable to get cell info");
536             exit(1);
537         }
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;
542         }
543         if (i < MAXSERVERS) serverList[i] = 0;
544     }
545
546     if (as->parms[aPRINCIPAL].items) {
547         strncpy(name, as->parms[aPRINCIPAL].items->data, sizeof(name) - 1);
548         name[sizeof(name) - 1] = '\0';
549     } else {
550         /* No explicit name provided: use Unix uid to get a name */
551         struct passwd *pw;
552         pw = getpwuid(getuid());
553         if (pw == 0) {
554             fprintf (stderr, "Can't determine your name from your user id.\n");
555             fprintf (stderr, "Try providing a principal name.\n");
556             exit(1);
557         }
558         strncpy(name, pw->pw_name, sizeof(name) - 1);
559         name[sizeof(name) - 1] = '\0';
560     }
561
562     if (as->parms[aPASSWORD].items) {
563         /*
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!
567          */
568         foundPassword = 1;
569         strncpy (passwd, as->parms[aPASSWORD].items->data, sizeof(passwd) - 1);
570         passwd[sizeof(passwd) - 1] = '\0';
571         memset(as->parms[aPASSWORD].items->data, 0, strlen(as->parms[aPASSWORD].items->data));
572     }
573
574     if (as->parms[aLIFETIME].items) {
575         char *life = as->parms[aLIFETIME].items->data;
576         char *sp;                       /* string ptr to rest of life */
577         lifetime = 3600*strtol (life, &sp, 0); /* hours */
578         if (sp == life) {
579 bad_lifetime:
580             fprintf (stderr, "%s: translating '%s' to lifetime\n",
581                      rn, life);
582             exit(1);
583         }
584         if (*sp == ':') {
585             life = sp+1;                /* skip the colon */
586             lifetime += 60*strtol (life, &sp, 0); /* minutes */
587             if (sp == life) goto bad_lifetime;
588             if (*sp == ':') {
589                 life = sp+1;
590                 lifetime += strtol (life, &sp, 0); /* seconds */
591                 if (sp == life) goto bad_lifetime;
592                 if (*sp) goto bad_lifetime;
593             } else if (*sp) goto bad_lifetime;
594         } else if (*sp) goto bad_lifetime;
595     } else lifetime = 0;
596
597     /*
598      * Make connections to all the servers.
599      */
600     rx_Init(0);
601     for (i = 0; i < MAXSERVERS; i++) {
602         if (! serverList[i]) {
603             serverconns[i] = 0;
604             break;
605         }
606         serverconns[i] = rx_NewConnection
607             (serverList[i], htons(ADK_PORT), ADK_SERVICE,
608              rxnull_NewClientSecurityObject(), 0);
609     }
610
611     /*
612      * Initialize ubik client gizmo to randomize the calls for us.
613      */
614     ubik_ClientInit(serverconns, &ubik_handle);
615
616     /*
617      * Come up with an acceptable nonce. V5 doc says that we just need
618      * time of day. Actually, for this app, I don't think anything
619      * is really needed. Better safe than sorry (although I wonder if
620      * it might have been better to encode the AFS ID in the nonce
621      * reply field--that's the one field that the intermediate server
622      * has total control over, and which can be securely transmitted
623      * back to the client).
624      */
625     gettimeofday(&now, 0);
626     nonce = now.tv_sec;
627
628     /*
629      * Ask our agent to get us a Kerberos V5 ticket.
630      */
631     reply_p = (adk_reply_ptr) 0;
632     error_p = (adk_error_ptr) 0;
633     code = ubik_Call
634         (ADK_GetTicket,
635          ubik_handle,
636          0,             /* Ubik flags */
637          name,          /* IN:  Principal: must be exact DCE principal */
638          nonce,         /* IN:  Input nonce */
639          lifetime,      /* IN:  lifetime */
640          &error_p,      /* OUT: Error, if any */
641          &reply_p);     /* OUT: KTC reply, if no error */
642
643     /*
644      * Destroy Rx connections on the off-chance this will allow less state
645      * to be preserved at the server.
646      */
647     ubik_ClientDestroy(ubik_handle);
648
649     /*
650      * Finalize Rx. This may allow connections at the server to wind down
651      * faster.
652      */
653     rx_Finalize();
654
655     /*
656      * Check for simple communication failures.
657      */
658     if (code) {
659         com_err(rn, code, "-- failed to contact authentication service");
660         exit(1);
661     }
662
663     /*
664      * Also check for DCE errors, which are interpreted for us by
665      * the translator.
666      */
667     if (error_p && error_p->code) {
668         fprintf(stderr, "dlog: %s\n", error_p->data);
669         exit(1);
670     }
671
672     /*
673      * Make sure the reply was filled in.
674      */
675     if (!reply_p) {
676         fprintf(stderr, "dlog: unexpected error in server response; aborted\n");
677         exit(1);
678     }
679
680     /*
681      * Get the password if it wasn't provided.
682      */
683     if (!foundPassword) {
684         if (readpipe) {
685             strcpy(passwd, getpipepass());
686         } else {
687             code = des_read_pw_string(passwd, sizeof(passwd), "Password:", 0);
688             if (code) {
689                 com_err (rn, code, "-- couldn't read password");
690                 exit(1);
691             }
692         }
693     }
694
695     /*
696      * Convert the password into the appropriate key block, given
697      * the salt passed back from the ADK_GetTicket call, above. Destroy
698      * the password.
699      */
700     if (strlen(passwd) + strlen(reply_p->salt) + 1 > sizeof(passwd)) {
701         fprintf(stderr, "dlog: unexpectedly long passwd/salt combination");
702         exit(1);
703     }
704     strcat(passwd, reply_p->salt);
705     des_string_to_key(passwd, passwd_key);
706     memset(passwd, 0, strlen(passwd));
707     
708     /*
709      * Decrypt the private data returned by the DCE KDC, and forwarded
710      * to us by the translator.
711      */
712     code = des_key_sched (passwd_key, schedule);
713     if (!code) {
714         code = des_cbc_encrypt
715             (reply_p->private.adk_code_val, reply_p->private.adk_code_val,
716              reply_p->private.adk_code_len, schedule, passwd_key, DECRYPT);
717     }
718     if (code) {
719         com_err(rn, code, "-- unable to decrypt reply from the DCE KDC");
720         exit(1);
721     }
722
723     /*
724      * Destroy the key block: it's no longer needed.
725      */
726     memset(schedule, 0, sizeof(schedule));
727     memset(passwd_key, 0, sizeof(passwd_key));
728
729     /*
730      * Do a very quick and dirty ASN.1 decode of the relevant parts
731      * of the private data.
732      *
733      * The decrypted data contains a 12-byte header (confounder and CRC-32
734      * checksum). We choose to ignore this.
735      */
736     code = decode_reply(reply_p->private.adk_code_val + 12,  /* Skip header */
737                         reply_p->private.adk_code_len - 12,  /* ditto */
738                         &kdcrep);
739     
740     if (code || kdcrep.nonce != nonce) {
741         fprintf(stderr, "dlog: DCE authentication failed -- your password is probably incorrect\n");
742         exit(1);
743     }
744
745     /*
746      * If the cell was not explicitly specified, then we hope that the local
747      * name for the cell is the same as the one in the ticket.
748      * If not, we should get an error when we store it, so the user will see
749      * the errant name at that time.
750      */
751     if (!explicit_cell) strcpy(realm, kdcrep.realm);
752
753     /*
754      * Make an AFS token out of the ticket and session key, and install it
755      * in the cache manager.
756      */
757     code = store_afs_token(reply_p->unix_id,
758                            realm,
759                            reply_p->tktype,
760                            reply_p->ticket.adk_code_val,
761                            reply_p->ticket.adk_code_len,
762                            kdcrep.session_key,
763                            kdcrep.starttime? kdcrep.starttime : kdcrep.authtime,
764                            kdcrep.endtime,
765                            dosetpag);
766
767     if (code) {
768         com_err("dlog", code, "-- failed to store tickets");
769         exit(1);
770     }
771
772     return 0;
773 }
774
775 #ifdef DLOG_TEST
776 /*
777  * Check the ASN.1 generalized time conversion routine, which assumes
778  * the restricted format defined in the Kerberos V5 document.
779  */
780
781 /*
782  * The times in this array were checked independently with the following perl
783  * script:
784  *
785  *   ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(shift);
786  *   $mon++; $year += 1900;
787  *   printf("%04d%02d%02d%02d%02d%02dZ\n", $year, $mon, $mday, $hour, $min, $sec);
788  */
789 struct test_times {
790     char *generalized_time_p;
791     afs_int32 unix_time;
792 } test_times[] = {
793     {"19700101000000Z", 0},
794     {"19930101000000Z", 725846400},
795     {"19940101000000Z", 757382400},
796     {"19940201000000Z", 760060800},
797     {"19940301000000Z", 762480000},
798     {"19940401000000Z", 765158400},
799     {"19950101000000Z", 788918400},
800     {"19950201000000Z", 791596800},
801     {"19950301000000Z", 794016000},
802     {"19950401000000Z", 796694400},
803     {"19950501000000Z", 799286400},
804     {"19950601000000Z", 801964800},
805     {"19950701000000Z", 804556800},
806     {"19950801000000Z", 807235200},
807     {"19950901000000Z", 809913600},
808     {"19951001000000Z", 812505600},
809     {"19951101000000Z", 815184000},
810     {"19951201000000Z", 817776000},
811     {"19951231235959Z", 820454399},
812     {"19960101000000Z", 820454400},
813     {"19960131000000Z", 823046400},
814     {"19960131235959Z", 823132799},
815     {"19960201000000Z", 823132800},
816     {"19960229000000Z", 825552000},
817     {"19960229235959Z", 825638399},
818     {"19960301000000Z", 825638400},
819     {"19960331000000Z", 828230400},
820     {"19960331235959Z", 828316799},
821     {"19970101000000Z", 852076800},
822     {"19980101000000Z", 883612800},
823     {"19990101000000Z", 915148800},
824     {"20000101000000Z", 946684800},
825     {"20010101000000Z", 978307200},
826     {"20020101000000Z", 1009843200},
827     {"20030101000000Z", 1041379200},
828     {"20040101000000Z", 1072915200},
829     {"20050101000000Z", 1104537600},
830     {"20380119031407Z", 2147483647},
831 };
832
833 self_test() {
834     int i;
835     int nerrors = 0;
836
837     for (i = 0; i < sizeof(test_times) / sizeof(test_times[0]); i++) {
838         struct test_times *t_p = &test_times[i];
839         afs_int32 status;
840         afs_int32 unix_time;
841
842         status = decode_asn_time
843             (t_p->generalized_time_p, strlen(t_p->generalized_time_p), &unix_time);
844         if (status) {
845             printf("dlog: decode of ASN.1 time %s failed\n",
846                    t_p->generalized_time_p);
847             nerrors++;
848         } else if (t_p->unix_time != unix_time) {
849             printf("dlog: ASN.1 time %s converted incorrectly to %lu (should be %lu)\n",
850                    t_p->generalized_time_p, unix_time, t_p->unix_time);
851         }
852     }
853
854     if (nerrors) {
855         fprintf(stderr, "dlog: self test failed\n");
856         return 1;
857     }
858     fprintf(stderr, "dlog: self test OK\n");
859     return 0;
860 }
861 #endif