1/*
2 * (c) Copyright 1995 HEWLETT-PACKARD COMPANY
3 *
4 * To anyone who acknowledges that this file is provided
5 * "AS IS" without any express or implied warranty:
6 * permission to use, copy, modify, and distribute this
7 * file for any purpose is hereby granted without fee,
8 * provided that the above copyright notice and this
9 * notice appears in all copies, and that the name of
10 * Hewlett-Packard Company not be used in advertising or
11 * publicity pertaining to distribution of the software
12 * without specific, written prior permission.  Hewlett-
13 * Packard Company makes no representations about the
14 * suitability of this software for any purpose.
15 *
16 */
17/*
18 * k5dcecon - Program to convert a K5 TGT to a DCE context,
19 * for use with DFS and its PAG.
20 *
21 * The program is designed to be called as a sub process,
22 * and return via stdout the name of the cache which implies
23 * the PAG which should be used. This program itself does not
24 * use the cache or PAG itself, so the PAG in the kernel for
25 * this program may not be set.
26 *
27 * The calling program can then use the name of the cache
28 * to set the KRB5CCNAME and PAG for itself and its children.
29 *
30 * If no ticket was passed, an attemplt to join an existing
31 * PAG will be made.
32 *
33 * If a forwarded K5 TGT is passed in, either a new DCE
34 * context will be created, or an existing one will be updated.
35 * If the same ticket was already used to create an existing
36 * context, it will be joined instead.
37 *
38 * Parts of this program are based on k5dceauth,c which was
39 * given to me by HP and by the k5dcelogin.c which I developed.
40 * A slightly different version of k5dcelogin.c, was added to
41 * DCE 1.2.2
42 *
43 * D. E. Engert 6/17/97 ANL
44 */
45
46#include <stdio.h>
47#include <stdlib.h>
48#include <fcntl.h>
49#include <sys/types.h>
50#include <dirent.h>
51#include <sys/stat.h>
52#include <locale.h>
53#include <pwd.h>
54#include <string.h>
55#include <time.h>
56
57#include <errno.h>
58#include "k5dce.h"
59
60#include <dce/sec_login.h>
61#include <dce/dce_error.h>
62#include <dce/passwd.h>
63
64/* #define DEBUG */
65#if defined(DEBUG)
66#define DEEDEBUG(A) fprintf(stderr,A); fflush(stderr)
67#define DEEDEBUG2(A,B) fprintf(stderr,A,B); fflush(stderr)
68#else
69#define DEEDEBUG(A)
70#define DEEDEBUG2(A,B)
71#endif
72
73#ifdef __hpux
74#define seteuid(A)		setresuid(-1,A,-1)
75#endif
76
77
78int k5dcecreate (uid_t, char *, char*, krb5_creds **);
79int k5dcecon (uid_t, char *, char *);
80int k5dcegettgt (krb5_ccache *, char *, char *, krb5_creds **);
81int k5dcematch (uid_t, char *, char *, off_t *, krb5_creds **);
82int k5dcesession (uid_t, char *, krb5_creds **, int *,krb5_flags);
83
84
85char *progname = "k5dcecon";
86static time_t now;
87
88#ifdef notdef
89#ifdef _AIX
90/*---------------------------------------------*/
91 /* AIX with DCE 1.1 does not have the com_err in the libdce.a
92  * do a half hearted job of substituting for it.
93  */
94void com_err(char *p1, int code, ...)
95{
96    int lst;
97    dce_error_string_t  err_string;
98    dce_error_inq_text(code, err_string, &lst);
99    fprintf(stderr,"Error %d in %s: %s\n", code, p1, err_string );
100}
101
102/*---------------------------------------------*/
103void krb5_init_ets()
104{
105
106}
107#endif
108#endif
109
110
111/*------------------------------------------------*/
112/* find a cache to use  for our new pag           */
113/* Since there is no simple way to determine which
114 * caches are associated with a pag, we will have
115 * do look around and see what makes most sense on
116 * different systems.
117 * on a Solaris system, and in the DCE source,
118 * the pags always start with a 41.
119 * this is not true on the IBM, where there does not
120 * appear to be any pattern.
121 *
122 * But since we are always certifing our creds when
123 * they are received, we can us that fact, and look
124 * at the first word of the associated data file
125 * to see that it has a "5". If not don't use.
126 */
127
128int k5dcesession(luid, pname, tgt, ppag, tflags)
129  uid_t luid;
130  char *pname;
131  krb5_creds **tgt;
132  int *ppag;
133  krb5_flags tflags;
134{
135  DIR *dirp;
136  struct dirent *direntp;
137  off_t size;
138  krb5_timestamp endtime;
139  int better = 0;
140  krb5_creds *xtgt;
141
142  char prev_name[17] = "";
143  krb5_timestamp prev_endtime;
144  off_t prev_size;
145  u_long prev_pag = 0;
146
147  char ccname[64] = "FILE:/opt/dcelocal/var/security/creds/";
148
149  error_status_t st;
150  sec_login_handle_t lcontext = 0;
151  dce_error_string_t  err_string;
152  int lst;
153
154  DEEDEBUG2("k5dcesession looking for flags %8.8x\n",tflags);
155
156  dirp = opendir("/opt/dcelocal/var/security/creds/");
157  if (dirp == NULL) {
158	return 1;
159  }
160
161  while ( (direntp = readdir( dirp )) != NULL ) {
162
163/*
164 * (but root has the ffffffff which we are not interested in)
165 */
166    if (!strncmp(direntp->d_name,"dcecred_",8)
167         && (strlen(direntp->d_name) == 16)) {
168
169      /* looks like a cache name, lets do the stat, etc */
170
171      strcpy(ccname+38,direntp->d_name);
172      if (!k5dcematch(luid, pname, ccname, &size, &xtgt))  {
173
174        /* it's one of our caches, see if it is better
175         * i.e. the endtime is farther, and if the endtimes
176         * are the same, take the larger, as he who has the
177         * most tickets wins.
178         * it must also had the same set of flags at least
179         * i.e. if the forwarded TGT is forwardable, this one must
180         * be as well.
181         */
182
183        DEEDEBUG2("Cache:%s",direntp->d_name);
184        DEEDEBUG2(" size:%d",size);
185		DEEDEBUG2(" flags:%8.8x",xtgt->ticket_flags);
186		DEEDEBUG2(" %s",ctime((time_t *)&xtgt->times.endtime));
187
188        if ((xtgt->ticket_flags & tflags) == tflags ) {
189          if (prev_name[0]) {
190            if (xtgt->times.endtime > prev_endtime) {
191              better = 1;
192            } else if ((xtgt->times.endtime = prev_endtime)
193                  && (size > prev_size)){
194              better = 1;
195	        }
196          } else {   /* the first */
197            if (xtgt->times.endtime >= now) {
198            better = 1;
199	        }
200          }
201          if (better) {
202            strcpy(prev_name, direntp->d_name);
203	  	    prev_endtime = xtgt->times.endtime;
204            prev_size = size;
205            sscanf(prev_name+8,"%8X",&prev_pag);
206			*tgt = xtgt;
207            better = 0;
208          }
209        }
210      }
211    }
212  }
213  (void)closedir( dirp );
214
215  if (!prev_name[0])
216	 return 1; /* failed to find one */
217
218   DEEDEBUG2("Best: %s\n",prev_name);
219
220   if (ppag)
221      *ppag = prev_pag;
222
223   strcpy(ccname+38,prev_name);
224   setenv("KRB5CCNAME",ccname,1);
225
226   return(0);
227}
228
229
230/*----------------------------------------------*/
231/* see if this cache is for this this principal */
232
233int k5dcematch(luid, pname, ccname, sizep, tgt)
234  uid_t luid;
235  char *pname;
236  char *ccname;
237  off_t *sizep;  /* size of the file */
238  krb5_creds **tgt;
239{
240
241  krb5_ccache cache;
242  struct stat stbuf;
243  char ccdata[256];
244  int fd;
245  int status;
246
247  /* DEEDEBUG2("k5dcematch called: cache=%s\n",ccname+38); */
248
249  if (!strncmp(ccname,"FILE:",5)) {
250
251    strcpy(ccdata,ccname+5);
252    strcat(ccdata,".data");
253
254    /* DEEDEBUG2("Checking the .data file for %s\n",ccdata); */
255
256    if (stat(ccdata, &stbuf))
257      return(1);
258
259    if (stbuf.st_uid != luid)
260      return(1);
261
262    if ((fd = open(ccdata,O_RDONLY)) == -1)
263      return(1);
264
265    if ((read(fd,&status,4)) != 4) {
266      close(fd);
267      return(1);
268    }
269
270    /* DEEDEBUG2(".data file status = %d\n", status); */
271
272    if (status != 5)
273     return(1);
274
275    if (stat(ccname+5, &stbuf))
276      return(1);
277
278    if (stbuf.st_uid != luid)
279      return(1);
280
281    *sizep = stbuf.st_size;
282  }
283
284  return(k5dcegettgt(&cache, ccname, pname, tgt));
285}
286
287
288/*----------------------------------------*/
289/* k5dcegettgt - get the tgt from a cache */
290
291int k5dcegettgt(pcache, ccname, pname, tgt)
292  krb5_ccache *pcache;
293  char *ccname;
294  char *pname;
295  krb5_creds **tgt;
296
297{
298  krb5_ccache cache;
299  krb5_cc_cursor cur;
300  krb5_creds creds;
301  int code;
302  int found = 1;
303  krb5_principal princ;
304  char *kusername;
305  krb5_flags flags;
306  char *sname, *realm, *tgtname = NULL;
307
308  /* Since DCE does not expose much of the Kerberos interface,
309   * we will have to use what we can. This means setting the
310   * KRB5CCNAME for each file we want to test
311   * We will also not worry about freeing extra cache structures
312   * as this this routine is also not exposed, and this should not
313   * effect this module.
314   * We should also free the creds contents, but that is not exposed
315   * either.
316   */
317
318  setenv("KRB5CCNAME",ccname,1);
319  cache = NULL;
320  *tgt = NULL;
321
322  if (code = krb5_cc_default(pcache)) {
323     com_err(progname, code, "while getting ccache");
324     goto return2;
325  }
326
327  DEEDEBUG("Got cache\n");
328  flags = 0;
329  if (code = krb5_cc_set_flags(*pcache, flags)) {
330    com_err(progname, code,"While setting flags");
331    goto return2;
332  }
333  DEEDEBUG("Set flags\n");
334  if (code = krb5_cc_get_principal(*pcache, &princ)) {
335	com_err(progname, code, "While getting princ");
336    goto return1;
337  }
338  DEEDEBUG("Got principal\n");
339  if (code = krb5_unparse_name(princ, &kusername)) {
340    com_err(progname, code, "While unparsing principal");
341    goto return1;
342  }
343
344  DEEDEBUG2("Unparsed to \"%s\"\n", kusername);
345  DEEDEBUG2("pname is \"%s\"\n", pname);
346  if (strcmp(kusername, pname)) {
347   DEEDEBUG("Principals not equal\n");
348   goto return1;
349  }
350  DEEDEBUG("Principals equal\n");
351
352  realm = strchr(pname,'@');
353  realm++;
354
355  if ((tgtname = malloc(9 + 2 * strlen(realm))) == 0) {
356       fprintf(stderr,"Malloc failed for tgtname\n");
357       goto return1;
358  }
359
360  strcpy(tgtname,"krbtgt/");
361  strcat(tgtname,realm);
362  strcat(tgtname,"@");
363  strcat(tgtname,realm);
364
365  DEEDEBUG2("Getting tgt %s\n", tgtname);
366  if (code = krb5_cc_start_seq_get(*pcache, &cur)) {
367    com_err(progname, code, "while starting to retrieve tickets");
368    goto return1;
369  }
370
371  while (!(code = krb5_cc_next_cred(*pcache, &cur, &creds))) {
372    krb5_creds *cred = &creds;
373
374    if (code = krb5_unparse_name(cred->server, &sname)) {
375      com_err(progname, code, "while unparsing server name");
376      continue;
377    }
378
379    if (strncmp(sname, tgtname, strlen(tgtname)) == 0) {
380      DEEDEBUG("FOUND\n");
381      if (code = krb5_copy_creds(&creds, tgt)) {
382        com_err(progname, code, "while copying TGT");
383        goto return1;
384      }
385      found = 0;
386      break;
387    }
388    /* we should do a krb5_free_cred_contents(creds); */
389  }
390
391  if (code = krb5_cc_end_seq_get(*pcache, &cur)) {
392    com_err(progname, code, "while finishing retrieval");
393    goto return2;
394  }
395
396return1:
397  flags = KRB5_TC_OPENCLOSE;
398  krb5_cc_set_flags(*pcache, flags); /* force a close */
399
400return2:
401  if (tgtname)
402    free(tgtname);
403
404  return(found);
405}
406
407
408/*------------------------------------------*/
409/* Convert a forwarded TGT to a DCE context */
410int k5dcecon(luid, luser, pname)
411  uid_t luid;
412  char *luser;
413  char *pname;
414{
415
416  krb5_creds *ftgt = NULL;
417  krb5_creds *tgt = NULL;
418  unsigned32 dfspag;
419  boolean32 reset_passwd = 0;
420  int lst;
421  dce_error_string_t  err_string;
422  char *shell_prog;
423  krb5_ccache fcache;
424  char *ccname;
425  char *kusername;
426  char *urealm;
427  char *cp;
428  int pag;
429  int code;
430  krb5_timestamp endtime;
431
432
433  /* If there is no cache to be converted, we should not be here */
434
435  if ((ccname = getenv("KRB5CCNAME")) == NULL) {
436    DEEDEBUG("No KRB5CCNAME\n");
437    return(1);
438  }
439
440  if (k5dcegettgt(&fcache, ccname, pname, &ftgt)) {
441    fprintf(stderr, "%s: Did not find TGT\n", progname);
442    return(1);
443  }
444
445
446  DEEDEBUG2("flags=%x\n",ftgt->ticket_flags);
447  if (!(ftgt->ticket_flags & TKT_FLG_FORWARDABLE)){
448    fprintf(stderr,"Ticket not forwardable\n");
449    return(0); /* but OK to continue */
450  }
451
452  setenv("KRB5CCNAME","",1);
453
454#define TKT_ACCEPTABLE (TKT_FLG_FORWARDABLE | TKT_FLG_PROXIABLE \
455         | TKT_FLG_MAY_POSTDATE | TKT_FLG_RENEWABLE | TKT_FLG_HW_AUTH \
456         | TKT_FLG_PRE_AUTH)
457
458  if (!k5dcesession(luid, pname, &tgt, &pag,
459        (ftgt->ticket_flags & TKT_ACCEPTABLE))) {
460    if (ftgt->times.endtime > tgt->times.endtime) {
461      DEEDEBUG("Updating existing cache\n");
462      return(k5dceupdate(&ftgt, pag));
463    } else {
464      DEEDEBUG("Using existing cache\n");
465      return(0); /* use the original one */
466    }
467  }
468    /* see if the tgts match up */
469
470  if ((code = k5dcecreate(luid, luser, pname, &ftgt))) {
471	return (code);
472  }
473
474  /*
475   * Destroy the Kerberos5 cred cache file.
476   * but dont care aout the return code.
477   */
478
479  DEEDEBUG("Destroying the old cache\n");
480  if ((code = krb5_cc_destroy(fcache))) {
481    com_err(progname, code, "while destroying Kerberos5 ccache");
482  }
483  return (0);
484}
485
486
487/*--------------------------------------------------*/
488/* k5dceupdate - update the cache with a new TGT    */
489/* Assumed that the KRB5CCNAME has been set         */
490
491int k5dceupdate(krbtgt, pag)
492   krb5_creds **krbtgt;
493   int pag;
494{
495
496  krb5_ccache ccache;
497  int code;
498
499  if (code = krb5_cc_default(&ccache)) {
500    com_err(progname, code, "while opening cache for update");
501    return(2);
502   }
503
504  if (code = ccache->ops->init(ccache,(*krbtgt)->client)) {
505    com_err(progname, code, "while reinitilizing cache");
506    return(3);
507  }
508
509    /* krb5_cc_store_cred */
510  if (code = ccache->ops->store(ccache, *krbtgt)) {
511    com_err(progname, code, "while updating cache");
512    return(2);
513  }
514
515  sec_login_pag_new_tgt(pag, (*krbtgt)->times.endtime);
516  return(0);
517}
518/*--------------------------------------------------*/
519/* k5dcecreate - create a new DCE context           */
520
521int k5dcecreate(luid, luser, pname, krbtgt)
522   uid_t luid;
523   char *luser;
524   char *pname;
525   krb5_creds **krbtgt;
526{
527
528    char *cp;
529    char *urealm;
530    char *username;
531    char *defrealm;
532    uid_t uid;
533
534    error_status_t st;
535    sec_login_handle_t lcontext = 0;
536    sec_login_auth_src_t auth_src = 0;
537    boolean32 reset_passwd = 0;
538    int lst;
539    dce_error_string_t  err_string;
540
541	setenv("KRB5CCNAME","",1); /* make sure it not misused */
542
543	uid = getuid();
544	DEEDEBUG2("uid=%d\n",uid);
545
546	/* if run as root, change to user, so as to have the
547	 * cache created for the local user even if cross-cell
548	 * If run as a user, let standard file protection work.
549	 */
550
551	if (uid == 0) {
552		if (seteuid(luid) < 0)
553			goto abort;
554	}
555
556	cp = strchr(pname,'@');
557	*cp = '\0';
558	urealm = ++cp;
559
560 DEEDEBUG2("basename=%s\n",cp);
561 DEEDEBUG2("realm=%s\n",urealm);
562
563    /* now build the username as a single string or a /.../cell/user
564     * if this is a cross cell
565     */
566
567	if ((username = malloc(7+strlen(pname)+strlen(urealm))) == 0) {
568         fprintf(stderr,"Malloc failed for username\n");
569         goto abort;
570    }
571    if (krb5_get_default_realm(&defrealm)) {
572        DEEDEBUG("krb5_get_default_realm failed\n");
573        goto abort;
574    }
575
576
577    if (!strcmp(urealm,defrealm)) {
578        strcpy(username,pname);
579    } else {
580        strcpy(username,"/.../");
581        strcat(username,urealm);
582        strcat(username,"/");
583        strcat(username,pname);
584    }
585
586    /*
587     * Setup a DCE login context
588     */
589
590    if (sec_login_setup_identity((unsigned_char_p_t)username,
591				 (sec_login_external_tgt|sec_login_proxy_cred),
592				 &lcontext, &st)) {
593	/*
594	 * Add our TGT.
595	 */
596	  DEEDEBUG("Adding our new TGT\n");
597	  sec_login_krb5_add_cred(lcontext, *krbtgt, &st);
598	  if (st) {
599	    dce_error_inq_text(st, err_string, &lst);
600	    fprintf(stderr,
601				"Error while adding credentials for %s because %s\n",
602				username, err_string);
603	    goto abort;
604	  }
605	  DEEDEBUG("validating and certifying\n");
606	  /*
607	   * Now "validate" and certify the identity,
608	   *  usually we would pass a password here, but...
609	   * sec_login_valid_and_cert_ident
610	   * sec_login_validate_identity
611	   */
612
613	  if (sec_login_validate_identity(lcontext, 0, &reset_passwd,
614		 &auth_src, &st)) {
615	    DEEDEBUG2("validate_identity st=%d\n",st);
616	    if (st) {
617		  dce_error_inq_text(st, err_string, &lst);
618		  fprintf(stderr, "Validation error for %s because %s\n",
619				 username, err_string);
620		  goto abort;
621	    }
622		if (!sec_login_certify_identity(lcontext,&st)) {
623			dce_error_inq_text(st, err_string, &lst);
624			fprintf(stderr,
625			"Credentials not certified because %s\n",err_string);
626		}
627	    if (reset_passwd) {
628		 fprintf(stderr,
629                "Password must be changed for %s\n", username);
630	    }
631	    if (auth_src == sec_login_auth_src_local) {
632		fprintf(stderr,
633			 "Credentials obtained from local registry for %s\n",
634			 username);
635	    }
636	    if (auth_src == sec_login_auth_src_overridden) {
637		  fprintf(stderr, "Validated %s from local override entry, no network credentials obtained\n", username);
638		  goto abort;
639
640	    }
641	    /*
642	     * Actually create the cred files.
643	     */
644		DEEDEBUG("Ceating new cred files.\n");
645	    sec_login_set_context(lcontext, &st);
646	    if (st) {
647		  dce_error_inq_text(st, err_string, &lst);
648		  fprintf(stderr,
649                "Unable to set context for %s because %s\n",
650		    username, err_string);
651		  goto abort;
652	    }
653
654        /*
655         * Now free up the local context and leave the
656         * network context with its pag
657         */
658#if 0
659        sec_login_release_context(&lcontext, &st);
660        if (st) {
661          dce_error_inq_text(st, err_string, &lst);
662          fprintf(stderr,
663               "Unable to release context for %s because %s\n",
664            username, err_string);
665          goto abort;
666        }
667#endif
668	}
669	else {
670	  DEEDEBUG2("validate failed %d\n",st);
671	  dce_error_inq_text(st, err_string, &lst);
672	  fprintf(stderr,
673             "Unable to validate %s because %s\n", username,
674			err_string);
675	  goto abort;
676	}
677  }
678  else {
679	dce_error_inq_text(st, err_string, &lst);
680	fprintf(stderr,
681          "Unable to setup login entry for %s because %s\n",
682       username, err_string);
683	  goto abort;
684  }
685
686 done:
687    /* if we were root, get back to root */
688
689    DEEDEBUG2("sec_login_inq_pag %8.8x\n",
690             sec_login_inq_pag(lcontext, &st));
691
692    if (uid == 0) {
693      seteuid(0);
694    }
695
696	DEEDEBUG("completed\n");
697	return(0);
698
699 abort:
700    if (uid == 0) {
701      seteuid(0);
702    }
703
704    DEEDEBUG("Aborting\n");
705    return(2);
706}
707
708
709
710/*-------------------------------------------------*/
711main(argc, argv)
712  int argc;
713  char *argv[];
714{
715  int status;
716  extern int optind;
717  extern char *optarg;
718  int rv;
719
720  char *lusername = NULL;
721  char *pname = NULL;
722  int fflag = 0;
723  struct passwd *pw;
724  uid_t luid;
725  uid_t myuid;
726  char *ccname;
727  krb5_creds *tgt = NULL;
728
729#ifdef DEBUG
730  close(2);
731  open("/tmp/k5dce.debug",O_WRONLY|O_CREAT|O_APPEND, 0600);
732#endif
733
734  if (myuid = getuid()) {
735    DEEDEBUG2("UID = %d\n",myuid);
736    exit(33); /* must be root to run this, get out now */
737  }
738
739  while ((rv = getopt(argc,argv,"l:p:fs")) != -1) {
740    DEEDEBUG2("Arg = %c\n", rv);
741    switch(rv) {
742      case 'l':         /* user name */
743	lusername = optarg;
744	DEEDEBUG2("Optarg = %s\n", optarg);
745	break;
746      case 'p':         /* principal name */
747        pname = optarg;
748	DEEDEBUG2("Optarg = %s\n", optarg);
749        break;
750      case 'f':         /* convert a forwarded TGT to a context */
751        fflag++;
752        break;
753      case 's':      /* old test parameter, ignore it */
754        break;
755    }
756  }
757
758  setlocale(LC_ALL, "");
759  krb5_init_ets();
760  time(&now); /* set time to check expired tickets */
761
762  /* if lusername == NULL, Then user is passed as the USER= variable */
763
764  if (!lusername) {
765    lusername = getenv("USER");
766    if (!lusername) {
767      fprintf(stderr, "USER not in environment\n");
768      return(3);
769    }
770  }
771
772  if ((pw = getpwnam(lusername)) == NULL) {
773    fprintf(stderr, "Who are you?\n");
774    return(44);
775  }
776
777  luid = pw->pw_uid;
778
779  if (fflag) {
780    status = k5dcecon(luid, lusername, pname);
781  } else {
782    status = k5dcesession(luid, pname, &tgt, NULL, 0);
783  }
784
785  if (!status) {
786    printf("%s",getenv("KRB5CCNAME")); /* return via stdout to caller */
787    DEEDEBUG2("KRB5CCNAME=%s\n",getenv("KRB5CCNAME"));
788  }
789
790  DEEDEBUG2("Returning status %d\n",status);
791  return (status);
792}
793