xref: /freebsd/crypto/krb5/src/clients/ksu/heuristic.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright (c) 1994 by the University of Southern California
4  *
5  * EXPORT OF THIS SOFTWARE from the United States of America may
6  *     require a specific license from the United States Government.
7  *     It is the responsibility of any person or organization contemplating
8  *     export to obtain such a license before exporting.
9  *
10  * WITHIN THAT CONSTRAINT, permission to copy, modify, and distribute
11  *     this software and its documentation in source and binary forms is
12  *     hereby granted, provided that any documentation or other materials
13  *     related to such distribution or use acknowledge that the software
14  *     was developed by the University of Southern California.
15  *
16  * DISCLAIMER OF WARRANTY.  THIS SOFTWARE IS PROVIDED "AS IS".  The
17  *     University of Southern California MAKES NO REPRESENTATIONS OR
18  *     WARRANTIES, EXPRESS OR IMPLIED.  By way of example, but not
19  *     limitation, the University of Southern California MAKES NO
20  *     REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
21  *     PARTICULAR PURPOSE. The University of Southern
22  *     California shall not be held liable for any liability nor for any
23  *     direct, indirect, or consequential damages with respect to any
24  *     claim by the user or distributor of the ksu software.
25  *
26  * KSU was written by:  Ari Medvinsky, ari@isi.edu
27  */
28 
29 #include "ksu.h"
30 
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 
35 
36 /*******************************************************************
37 get_all_princ_from_file - retrieves all principal names
38                         from file pointed to by fp.
39 
40 *******************************************************************/
41 static void close_time (int, FILE *, int, FILE *);
42 static krb5_boolean find_str_in_list (char **, char *);
43 
44 krb5_error_code
get_all_princ_from_file(FILE * fp,char *** plist)45 get_all_princ_from_file(FILE *fp, char ***plist)
46 {
47 
48     krb5_error_code retval;
49     char * line, * fprinc, * lp, ** temp_list = NULL;
50     int count = 0, chunk_count = 1;
51 
52     if (!(temp_list = (char **) malloc( CHUNK * sizeof(char *))))
53         return ENOMEM;
54 
55     retval = get_line(fp, &line);
56     if (retval)
57         return retval;
58 
59     while (line){
60         fprinc = get_first_token (line, &lp);
61 
62         if (fprinc ){
63             temp_list[count] = xstrdup(fprinc);
64             count ++;
65         }
66 
67         if(count == (chunk_count * CHUNK -1)){
68             chunk_count ++;
69             if (!(temp_list = (char **) realloc(temp_list,
70                                                 chunk_count * CHUNK * sizeof(char *)))){
71                 return ENOMEM;
72             }
73         }
74 
75 
76         free (line);
77         retval = get_line(fp, &line);
78         if (retval)
79             return retval;
80     }
81 
82     temp_list[count] = NULL;
83 
84     *plist = temp_list;
85     return 0;
86 }
87 
88 /*************************************************************
89 list_union - combines list1 and list2 into combined_list.
90              the  space for list1 and list2 is either freed
91              or used by combined_list.
92 **************************************************************/
93 
94 krb5_error_code
list_union(char ** list1,char ** list2,char *** combined_list)95 list_union(char **list1, char **list2, char ***combined_list)
96 {
97 
98     unsigned int c1 =0, c2 = 0, i=0, j=0;
99     char ** tlist;
100 
101     if (! list1){
102         *combined_list = list2;
103         return 0;
104     }
105 
106     if (! list2){
107         *combined_list = list1;
108         return 0;
109     }
110 
111     while (list1[c1]) c1++;
112     while (list2[c2]) c2++;
113 
114     if (!(tlist = (char **) calloc( c1 + c2 + 1, sizeof ( char *))))
115         return ENOMEM;
116 
117     i = 0;
118     while(list1[i]) {
119         tlist[i] = list1[i];
120         i++;
121     }
122     j = 0;
123     while(list2[j]){
124         if(find_str_in_list(list1, list2[j])==FALSE){
125             tlist[i] = list2[j];
126             i++;
127         }
128         j++;
129     }
130 
131     free (list1);
132     free (list2);
133 
134     tlist[i]= NULL;
135 
136     *combined_list = tlist;
137     return 0;
138 }
139 
140 krb5_error_code
filter(FILE * fp,char * cmd,char ** k5users_list,char *** k5users_filt_list)141 filter(FILE *fp, char *cmd, char **k5users_list, char ***k5users_filt_list)
142 {
143 
144     krb5_error_code retval =0;
145     krb5_boolean found = FALSE;
146     char * out_cmd = NULL;
147     unsigned int i=0, j=0, found_count = 0, k=0;
148     char ** temp_filt_list;
149 
150     *k5users_filt_list = NULL;
151 
152     if (k5users_list == NULL)
153         return 0;
154 
155     while(k5users_list[i]){
156         free(out_cmd);
157         out_cmd = NULL;
158 
159         retval= k5users_lookup(fp, k5users_list[i], cmd, &found, &out_cmd);
160         if (retval)
161             goto cleanup;
162 
163         if (found == FALSE){
164             free (k5users_list[i]);
165             k5users_list[i] = NULL;
166             if (out_cmd) {
167                 gb_err = out_cmd;
168                 out_cmd = NULL;
169             }
170         } else
171             found_count ++;
172 
173         i++;
174     }
175 
176     temp_filt_list = xcalloc(found_count + 1, sizeof(*temp_filt_list));
177 
178     for(j= 0, k=0; j < i; j++ ) {
179         if (k5users_list[j]){
180             temp_filt_list[k] = k5users_list[j];
181             k++;
182         }
183     }
184 
185     temp_filt_list[k] = NULL;
186 
187     free (k5users_list);
188 
189     *k5users_filt_list = temp_filt_list;
190 
191 cleanup:
192     free(out_cmd);
193     return retval;
194 }
195 
196 krb5_error_code
get_authorized_princ_names(const char * luser,char * cmd,char *** princ_list)197 get_authorized_princ_names(const char *luser, char *cmd, char ***princ_list)
198 {
199 
200     struct passwd *pwd;
201     int k5login_flag =0;
202     int k5users_flag =0;
203     FILE * login_fp = NULL , * users_fp = NULL;
204     char **  k5login_list = NULL, ** k5users_list = NULL;
205     char ** k5users_filt_list = NULL;
206     char ** combined_list = NULL;
207     struct stat tb;
208     krb5_error_code retval;
209 
210     *princ_list = NULL;
211 
212     /* no account => no access */
213 
214     if ((pwd = getpwnam(luser)) == NULL)
215         return 0;
216 
217     k5login_flag = stat(k5login_path, &tb);
218     k5users_flag = stat(k5users_path, &tb);
219 
220     if (!k5login_flag){
221         if ((login_fp = fopen(k5login_path, "r")) == NULL)
222             return 0;
223         if ( fowner(login_fp, pwd->pw_uid) == FALSE){
224             close_time(1 /*k5users_flag*/, (FILE *) 0 /*users_fp*/,
225                        k5login_flag,login_fp);
226             return 0;
227         }
228     }
229     if (!k5users_flag){
230         users_fp = fopen(k5users_path, "r");
231         if (users_fp == NULL) {
232             close_time(1, NULL, k5login_flag, login_fp);
233             return 0;
234         }
235 
236         if ( fowner(users_fp, pwd->pw_uid) == FALSE){
237             close_time(k5users_flag,users_fp, k5login_flag,login_fp);
238             return 0;
239         }
240 
241         retval = get_all_princ_from_file (users_fp, &k5users_list);
242         if(retval) {
243             close_time(k5users_flag,users_fp, k5login_flag,login_fp);
244             return retval;
245         }
246 
247         rewind(users_fp);
248 
249         retval = filter(users_fp,cmd, k5users_list, &k5users_filt_list);
250         if(retval) {
251             close_time(k5users_flag,users_fp, k5login_flag, login_fp);
252             return retval;
253         }
254     }
255 
256     if (!k5login_flag){
257         retval = get_all_princ_from_file (login_fp, &k5login_list);
258         if(retval) {
259             close_time(k5users_flag,users_fp, k5login_flag,login_fp);
260             return retval;
261         }
262     }
263 
264     close_time(k5users_flag,users_fp, k5login_flag, login_fp);
265 
266     retval = list_union(k5login_list, k5users_filt_list, &combined_list);
267     if (retval){
268         return retval;
269     }
270     *princ_list = combined_list;
271     return 0;
272 }
273 
274 static void
close_time(int k5users_flag,FILE * users_fp,int k5login_flag,FILE * login_fp)275 close_time(int k5users_flag, FILE *users_fp, int k5login_flag, FILE *login_fp)
276 {
277 
278     if (!k5users_flag) fclose(users_fp);
279     if (!k5login_flag) fclose(login_fp);
280 
281 }
282 
283 static krb5_boolean
find_str_in_list(char ** list,char * elm)284 find_str_in_list(char **list, char *elm)
285 {
286 
287     int i=0;
288     krb5_boolean found = FALSE;
289 
290     if (!list) return found;
291 
292     while (list[i] ){
293         if (!strcmp(list[i], elm)){
294             found = TRUE;
295             break;
296         }
297         i++;
298     }
299 
300     return found;
301 }
302 
303 /**********************************************************************
304 returns the principal that is closes to client (can be the the client
305 himself). plist contains
306 a principal list obtained from .k5login and .k5users file.
307 A principal is picked that has the best chance of getting in.
308 
309 **********************************************************************/
310 
311 krb5_error_code
get_closest_principal(krb5_context context,char ** plist,krb5_principal * client,krb5_boolean * found)312 get_closest_principal(krb5_context context, char **plist,
313                       krb5_principal *client, krb5_boolean *found)
314 {
315     krb5_error_code retval =0;
316     krb5_principal temp_client, best_client = NULL;
317     int i = 0, j=0, cnelem, pnelem;
318     krb5_boolean got_one;
319 
320     *found = FALSE;
321 
322     if (! plist ) return 0;
323 
324     cnelem = krb5_princ_size(context, *client);
325 
326     while(plist[i]){
327 
328         retval = krb5_parse_name(context, plist[i], &temp_client);
329         if (retval)
330             goto cleanup;
331 
332         pnelem = krb5_princ_size(context, temp_client);
333 
334         if ( cnelem > pnelem){
335             i++;
336             continue;
337         }
338 
339         if (data_eq(*krb5_princ_realm(context, *client),
340                     *krb5_princ_realm(context, temp_client))) {
341 
342             got_one = TRUE;
343             for(j =0; j < cnelem; j ++){
344                 krb5_data *p1 =
345                     krb5_princ_component(context, *client, j);
346                 krb5_data *p2 =
347                     krb5_princ_component(context, temp_client, j);
348 
349                 if (!p1 || !p2 || !data_eq(*p1, *p2)) {
350                     got_one = FALSE;
351                     break;
352                 }
353             }
354             if (got_one == TRUE){
355                 if(best_client){
356                     if(krb5_princ_size(context, best_client) >
357                        krb5_princ_size(context, temp_client)){
358                         krb5_free_principal(context, best_client);
359                         best_client = temp_client;
360                     }
361                 }else
362                     best_client = temp_client;
363             }
364         }
365         i++;
366     }
367 
368     if (best_client) {
369         *found = TRUE;
370         *client = best_client;
371         best_client = NULL;
372     }
373 
374 cleanup:
375     krb5_free_principal(context, best_client);
376     return retval;
377 }
378 
379 /****************************************************************
380 find_either_ticket checks to see whether there is a ticket for the
381    end server or tgt, if neither is there the return FALSE,
382 *****************************************************************/
383 
384 krb5_error_code
find_either_ticket(krb5_context context,krb5_ccache cc,krb5_principal client,krb5_principal end_server,krb5_boolean * found)385 find_either_ticket(krb5_context context, krb5_ccache cc, krb5_principal client,
386                    krb5_principal end_server, krb5_boolean *found)
387 {
388 
389     krb5_principal kdc_server;
390     krb5_error_code retval;
391     krb5_boolean temp_found = FALSE;
392 
393     if (ks_ccache_is_initialized(context, cc)) {
394 
395         retval = find_ticket(context, cc, client, end_server, &temp_found);
396         if (retval)
397             return retval;
398 
399         if (temp_found == FALSE){
400             retval = ksu_tgtname(context,
401                                  krb5_princ_realm(context, client),
402                                  krb5_princ_realm(context, client),
403                                  &kdc_server);
404             if (retval)
405                 return retval;
406 
407             retval = find_ticket(context, cc,client, kdc_server, &temp_found);
408             if(retval)
409                 return retval;
410         }
411         else if (auth_debug)
412             printf("find_either_ticket: found end server ticket\n");
413     }
414 
415     *found = temp_found;
416 
417     return 0;
418 }
419 
420 krb5_error_code
find_ticket(krb5_context context,krb5_ccache cc,krb5_principal client,krb5_principal server,krb5_boolean * found)421 find_ticket(krb5_context context, krb5_ccache cc, krb5_principal client,
422             krb5_principal server, krb5_boolean *found)
423 {
424 
425     krb5_creds tgt, tgtq;
426     krb5_error_code retval;
427 
428     *found = FALSE;
429 
430     memset(&tgtq, 0, sizeof(tgtq));
431     memset(&tgt, 0, sizeof(tgt));
432 
433     retval= krb5_copy_principal(context,  client, &tgtq.client);
434     if (retval)
435         return retval;
436 
437     retval= krb5_copy_principal(context,  server, &tgtq.server);
438     if (retval)
439         return retval ;
440 
441     retval = krb5_cc_retrieve_cred(context, cc, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
442                                    &tgtq, &tgt);
443 
444     if (! retval) retval = krb5_check_exp(context, tgt.times);
445 
446     if (retval){
447         if ((retval != KRB5_CC_NOTFOUND) &&
448             (retval != KRB5KRB_AP_ERR_TKT_EXPIRED)){
449             return retval ;
450         }
451     } else{
452         *found = TRUE;
453         return 0;
454     }
455 
456     free(tgtq.server);
457     free(tgtq.client);
458 
459     return 0;
460 }
461 
462 krb5_error_code
find_princ_in_list(krb5_context context,krb5_principal princ,char ** plist,krb5_boolean * found)463 find_princ_in_list(krb5_context context, krb5_principal princ, char **plist,
464                    krb5_boolean *found)
465 {
466 
467     int i=0;
468     char * princname;
469     krb5_error_code retval;
470 
471     *found = FALSE;
472 
473     if (!plist) return 0;
474 
475     retval = krb5_unparse_name(context, princ, &princname);
476     if (retval)
477         return retval;
478 
479     while (plist[i] ){
480         if (!strcmp(plist[i], princname)){
481             *found = TRUE;
482             break;
483         }
484         i++;
485     }
486 
487     free(princname);
488     return 0;
489 
490 }
491 
492 typedef struct princ_info {
493     krb5_principal p;
494     krb5_boolean found;
495 }princ_info;
496 
497 /**********************************************************************
498 get_best_princ_for_target -
499 
500 sets the client name, path_out gets set, if authorization is not possible
501 path_out gets set to ...
502 
503 ***********************************************************************/
504 
505 krb5_error_code
get_best_princ_for_target(krb5_context context,uid_t source_uid,uid_t target_uid,char * source_user,char * target_user,krb5_ccache cc_source,krb5_get_init_creds_opt * options,char * cmd,char * hostname,krb5_principal * client,int * path_out)506 get_best_princ_for_target(krb5_context context, uid_t source_uid,
507                           uid_t target_uid, char *source_user,
508                           char *target_user, krb5_ccache cc_source,
509                           krb5_get_init_creds_opt *options, char *cmd,
510                           char *hostname, krb5_principal *client,
511                           int *path_out)
512 {
513 
514     princ_info princ_trials[10];
515     krb5_principal cc_def_princ = NULL, temp_client = NULL;
516     krb5_principal target_client = NULL, source_client = NULL;
517     krb5_principal end_server = NULL;
518     krb5_error_code retval;
519     char ** aplist =NULL;
520     krb5_boolean found = FALSE;
521     struct stat tb;
522     int count =0;
523     int i;
524 
525     *path_out = 0;
526 
527     /* -n option was specified client is set we are done */
528     if (*client != NULL)
529         return 0;
530 
531     if (ks_ccache_is_initialized(context, cc_source)) {
532         retval = krb5_cc_get_principal(context, cc_source, &cc_def_princ);
533         if (retval)
534             goto cleanup;
535     }
536 
537     retval=krb5_parse_name(context, target_user, &target_client);
538     if (retval)
539         goto cleanup;
540 
541     retval=krb5_parse_name(context, source_user, &source_client);
542     if (retval)
543         goto cleanup;
544 
545     if (source_uid == 0) {
546         if (target_uid != 0) {
547             /* This will be used to restrict the cache copy. */
548             *client = target_client;
549             target_client = NULL;
550         } else if (cc_def_princ != NULL) {
551             *client = cc_def_princ;
552             cc_def_princ = NULL;
553         } else {
554             *client = target_client;
555             target_client = NULL;
556         }
557         if (auth_debug)
558             printf(" GET_best_princ_for_target: via source_uid == 0\n");
559         goto cleanup;
560     }
561 
562     /* from here on, the code is for source_uid !=  0 */
563 
564     if (source_uid && (source_uid == target_uid)){
565         if (cc_def_princ != NULL) {
566             *client = cc_def_princ;
567             cc_def_princ = NULL;
568         } else {
569             *client = target_client;
570             target_client = NULL;
571         }
572         if (auth_debug)
573             printf("GET_best_princ_for_target: via source_uid == target_uid\n");
574         goto cleanup;
575     }
576 
577     /* Become root, then target for looking at .k5login.*/
578     if (krb5_seteuid(0) || krb5_seteuid(target_uid) ) {
579         retval = errno;
580         goto cleanup;
581     }
582 
583     /* if .k5users and .k5login do not exist */
584     if (stat(k5login_path, &tb) && stat(k5users_path, &tb) ){
585         *client = target_client;
586         target_client = NULL;
587 
588         if (cmd)
589             *path_out = NOT_AUTHORIZED;
590 
591         if (auth_debug)
592             printf(" GET_best_princ_for_target: via no auth files path\n");
593 
594         goto cleanup;
595     }else{
596         retval = get_authorized_princ_names(target_user, cmd, &aplist);
597         if (retval)
598             goto cleanup;
599 
600         /* .k5users or .k5login exist, but no authorization */
601         if ((!aplist) || (!aplist[0])) {
602             *path_out = NOT_AUTHORIZED;
603             if (auth_debug)
604                 printf("GET_best_princ_for_target: via empty auth files path\n");
605             goto cleanup;
606         }
607     }
608 
609     retval = krb5_sname_to_principal(context, hostname, NULL,
610                                      KRB5_NT_SRV_HST, &end_server);
611     if (retval)
612         goto cleanup;
613 
614     /* first see if default principal of the source cache
615      * can get us in, then the target_user@realm, then the
616      * source_user@realm. If all of them fail, try any
617      * other ticket in the cache. */
618 
619     if (cc_def_princ)
620         princ_trials[count ++].p = cc_def_princ;
621     else
622         princ_trials[count ++].p = NULL;
623 
624     princ_trials[count ++].p = target_client;
625     princ_trials[count ++].p = source_client;
626 
627     for (i= 0; i < count; i ++)
628         princ_trials[i].found = FALSE;
629 
630     for (i= 0; i < count; i ++){
631         if(princ_trials[i].p) {
632             retval= find_princ_in_list(context, princ_trials[i].p, aplist,
633                                        &found);
634             if (retval)
635                 goto cleanup;
636 
637             if (found == TRUE){
638                 princ_trials[i].found = TRUE;
639 
640                 retval = find_either_ticket (context, cc_source,
641                                              princ_trials[i].p,
642                                              end_server, &found);
643                 if (retval)
644                     goto cleanup;
645                 if (found == TRUE){
646                     retval = krb5_copy_principal(context, princ_trials[i].p,
647                                                  client);
648                     if (auth_debug)
649                         printf("GET_best_princ_for_target: via ticket file, choice #%d\n", i);
650                     goto cleanup;
651                 }
652             }
653         }
654     }
655 
656     /* out of preferred principals, see if there is any ticket that will
657        get us in */
658 
659     i=0;
660     while (aplist[i]){
661         retval = krb5_parse_name(context, aplist[i], &temp_client);
662         if (retval)
663             goto cleanup;
664 
665         retval = find_either_ticket (context, cc_source, temp_client,
666                                      end_server, &found);
667         if (retval)
668             goto cleanup;
669 
670         if (found == TRUE){
671             if (auth_debug)
672                 printf("GET_best_princ_for_target: via ticket file, choice: any ok ticket \n" );
673             *client = temp_client;
674             temp_client = NULL;
675             goto cleanup;
676         }
677 
678         krb5_free_principal(context, temp_client);
679         temp_client = NULL;
680 
681         i++;
682     }
683 
684     /* no tickets qualified, select a principal, that may be used
685        for password promting */
686 
687 
688     for (i=0; i < count; i ++){
689         if (princ_trials[i].found == TRUE){
690             retval = krb5_copy_principal(context, princ_trials[i].p, client);
691 
692             if (auth_debug)
693                 printf("GET_best_princ_for_target: via prompt passwd list choice #%d \n",i);
694             goto cleanup;
695         }
696     }
697 
698 #ifdef PRINC_LOOK_AHEAD
699     for (i=0; i < count; i ++){
700         if (princ_trials[i].p){
701             retval=krb5_copy_principal(context, princ_trials[i].p,
702                                        &temp_client);
703             if(retval)
704                 goto cleanup;
705 
706             /* get the client name that is the closest
707                to the three princ in trials */
708 
709             retval=get_closest_principal(context, aplist, &temp_client,
710                                          &found);
711             if(retval)
712                 goto cleanup;
713 
714             if (found == TRUE){
715                 *client = temp_client;
716                 temp_client = NULL;
717                 if (auth_debug)
718                     printf("GET_best_princ_for_target: via prompt passwd list choice: approximation of princ in trials # %d \n",i);
719                 goto cleanup;
720             }
721         }
722     }
723 
724 #endif /* PRINC_LOOK_AHEAD */
725 
726 
727     if(auth_debug)
728         printf( "GET_best_princ_for_target: out of luck, can't get appropriate default principal\n");
729 
730     *path_out = NOT_AUTHORIZED;
731     retval = 0;
732 
733 cleanup:
734     krb5_free_principal(context, cc_def_princ);
735     krb5_free_principal(context, target_client);
736     krb5_free_principal(context, source_client);
737     krb5_free_principal(context, temp_client);
738     krb5_free_principal(context, end_server);
739     return retval;
740 }
741