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