xref: /freebsd/crypto/heimdal/appl/su/su.c (revision 3c4ba5f55438f7afd4f4b0b56f88f2bb505fd6a6)
1 /*
2  * Copyright (c) 1999 - 2008 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of KTH nor the names of its contributors may be
18  *    used to endorse or promote products derived from this software without
19  *    specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32 
33 #include <config.h>
34 
35 RCSID("$Id$");
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 #include <syslog.h>
42 
43 #ifdef HAVE_PATHS_H
44 #include <paths.h>
45 #endif
46 
47 #ifdef HAVE_SHADOW_H
48 #include <shadow.h>
49 #endif
50 
51 #include <pwd.h>
52 #ifdef HAVE_CRYPT_H
53 #include <crypt.h>
54 #endif
55 
56 #include "crypto-headers.h"
57 #ifdef KRB5
58 #include <krb5.h>
59 #endif
60 #include <kafs.h>
61 #include <err.h>
62 #include <roken.h>
63 #include <getarg.h>
64 
65 #include "supaths.h"
66 
67 #if !HAVE_DECL_ENVIRON
68 extern char **environ;
69 #endif
70 
71 int kerberos_flag = 1;
72 int csh_f_flag;
73 int full_login;
74 int env_flag;
75 char *kerberos_instance = "root";
76 int help_flag;
77 int version_flag;
78 char *cmd;
79 char tkfile[256];
80 
81 struct getargs args[] = {
82     { "kerberos", 'K', arg_negative_flag, &kerberos_flag,
83       "don't use kerberos" },
84     { NULL,	  'f', arg_flag,	  &csh_f_flag,
85       "don't read .cshrc" },
86     { "full",	  'l', arg_flag,          &full_login,
87       "simulate full login" },
88     { NULL,	  'm', arg_flag,          &env_flag,
89       "leave environment unmodified" },
90     { "instance", 'i', arg_string,        &kerberos_instance,
91       "root instance to use" },
92     { "command",  'c', arg_string,        &cmd,
93       "command to execute" },
94     { "help", 	  'h', arg_flag,          &help_flag },
95     { "version",  0,   arg_flag,          &version_flag },
96 };
97 
98 
99 static void
100 usage (int ret)
101 {
102     arg_printusage (args,
103 		    sizeof(args)/sizeof(*args),
104 		    NULL,
105 		    "[login [shell arguments]]");
106     exit (ret);
107 }
108 
109 static void
110 free_info(struct passwd *p)
111 {
112     free (p->pw_name);
113     free (p->pw_passwd);
114     free (p->pw_dir);
115     free (p->pw_shell);
116     free (p);
117 }
118 
119 static struct passwd*
120 dup_info(const struct passwd *pwd)
121 {
122     struct passwd *info;
123 
124     info = malloc(sizeof(*info));
125     if(info == NULL)
126 	return NULL;
127     info->pw_name = strdup(pwd->pw_name);
128     info->pw_passwd = strdup(pwd->pw_passwd);
129     info->pw_uid = pwd->pw_uid;
130     info->pw_gid = pwd->pw_gid;
131     info->pw_dir = strdup(pwd->pw_dir);
132     info->pw_shell = strdup(pwd->pw_shell);
133     if(info->pw_name == NULL || info->pw_passwd == NULL ||
134        info->pw_dir == NULL || info->pw_shell == NULL) {
135 	free_info (info);
136 	return NULL;
137     }
138     return info;
139 }
140 
141 #ifdef KRB5
142 static krb5_context context;
143 static krb5_ccache ccache;
144 
145 static int
146 krb5_verify(const struct passwd *login_info,
147 	    const struct passwd *su_info,
148 	    const char *kerberos_instance)
149 {
150     krb5_error_code ret;
151     krb5_principal p;
152     krb5_realm *realms, *r;
153     char *login_name = NULL;
154     int user_ok = 0;
155 
156 #if defined(HAVE_GETLOGIN) && !defined(POSIX_GETLOGIN)
157     login_name = getlogin();
158 #endif
159     ret = krb5_init_context (&context);
160     if (ret) {
161 #if 0
162 	warnx("krb5_init_context failed: %d", ret);
163 #endif
164 	return 1;
165     }
166 
167     ret = krb5_get_default_realms(context, &realms);
168     if (ret)
169 	return 1;
170 
171     /* Check all local realms */
172     for (r = realms; *r != NULL && !user_ok; r++) {
173 
174 	if (login_name == NULL || strcmp (login_name, "root") == 0)
175 	    login_name = login_info->pw_name;
176 	if (strcmp (su_info->pw_name, "root") == 0)
177 	    ret = krb5_make_principal(context, &p, *r,
178 				      login_name,
179 				      kerberos_instance,
180 				      NULL);
181 	else
182 	    ret = krb5_make_principal(context, &p, *r,
183 				      su_info->pw_name,
184 				      NULL);
185 	if (ret) {
186 	    krb5_free_host_realm(context, realms);
187 	    return 1;
188 	}
189 
190 	/* if we are su-ing too root, check with krb5_kuserok */
191 	if (su_info->pw_uid == 0 && !krb5_kuserok(context, p, su_info->pw_name))
192 	    continue;
193 
194 	ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
195 	if(ret) {
196 	    krb5_free_host_realm(context, realms);
197 	    krb5_free_principal (context, p);
198 	    return 1;
199 	}
200   	ret = krb5_verify_user(context, p, ccache, NULL, TRUE, NULL);
201 	krb5_free_principal (context, p);
202 	switch (ret) {
203 	case 0:
204 	    user_ok = 1;
205 	    break;
206 	case KRB5_LIBOS_PWDINTR :
207 	    krb5_cc_destroy(context, ccache);
208 	    break;
209 	case KRB5KRB_AP_ERR_BAD_INTEGRITY:
210 	case KRB5KRB_AP_ERR_MODIFIED:
211 	    krb5_cc_destroy(context, ccache);
212 	    krb5_warnx(context, "Password incorrect");
213 	    break;
214 	default :
215 	    krb5_cc_destroy(context, ccache);
216 	    krb5_warn(context, ret, "krb5_verify_user");
217 	    break;
218 	}
219     }
220     krb5_free_host_realm(context, realms);
221     if (!user_ok)
222 	return 1;
223     return 0;
224 }
225 
226 static int
227 krb5_start_session(void)
228 {
229     krb5_ccache ccache2;
230     char *cc_name;
231     int ret;
232 
233     ret = krb5_cc_new_unique(context, krb5_cc_type_file, NULL, &ccache2);
234     if (ret) {
235 	krb5_cc_destroy(context, ccache);
236 	return 1;
237     }
238 
239     ret = krb5_cc_copy_cache(context, ccache, ccache2);
240     if (ret) {
241 	krb5_cc_destroy(context, ccache);
242 	krb5_cc_destroy(context, ccache2);
243 	return 1;
244     }
245 
246     ret = asprintf(&cc_name, "%s:%s", krb5_cc_get_type(context, ccache2),
247 		   krb5_cc_get_name(context, ccache2));
248     if (ret == -1) {
249 	krb5_cc_destroy(context, ccache);
250 	krb5_cc_destroy(context, ccache2);
251 	errx(1, "malloc - out of memory");
252     }
253     esetenv("KRB5CCNAME", cc_name, 1);
254 
255     /* convert creds? */
256     if(k_hasafs()) {
257 	if (k_setpag() == 0)
258 	    krb5_afslog(context, ccache2, NULL, NULL);
259     }
260 
261     krb5_cc_close(context, ccache2);
262     krb5_cc_destroy(context, ccache);
263     return 0;
264 }
265 #endif
266 
267 
268 #define GROUP_MEMBER		0
269 #define GROUP_MISSING		1
270 #define GROUP_EMPTY		2
271 #define GROUP_NOT_MEMBER	3
272 
273 static int
274 group_member_p(const char *group, const char *user)
275 {
276     struct group *g;
277     int i;
278     g = getgrnam(group);
279     if(g == NULL)
280 	return GROUP_MISSING;
281     if(g->gr_mem[0] == NULL)
282 	return GROUP_EMPTY;
283     for(i = 0; g->gr_mem[i] != NULL; i++)
284 	if(strcmp(user, g->gr_mem[i]) == 0)
285 	    return GROUP_MEMBER;
286     return GROUP_NOT_MEMBER;
287 }
288 
289 static int
290 verify_unix(struct passwd *login, struct passwd *su)
291 {
292     char prompt[128];
293     char pw_buf[1024];
294     char *pw;
295     int r;
296     if(su->pw_passwd != NULL && *su->pw_passwd != '\0') {
297 	snprintf(prompt, sizeof(prompt), "%s's password: ", su->pw_name);
298 	r = UI_UTIL_read_pw_string(pw_buf, sizeof(pw_buf), prompt, 0);
299 	if(r != 0)
300 	    exit(0);
301 	pw = crypt(pw_buf, su->pw_passwd);
302 	memset(pw_buf, 0, sizeof(pw_buf));
303 	if(strcmp(pw, su->pw_passwd) != 0) {
304 	    syslog (LOG_ERR | LOG_AUTH, "%s to %s: incorrect password",
305 		    login->pw_name, su->pw_name);
306 	    return 1;
307 	}
308     }
309     /* if su:ing to root, check membership of group wheel or root; if
310        that group doesn't exist, or is empty, allow anyone to su
311        root */
312     if(su->pw_uid == 0) {
313 #ifndef ROOT_GROUP
314 #define ROOT_GROUP "wheel"
315 #endif
316 	int gs = group_member_p(ROOT_GROUP, login->pw_name);
317 	if(gs == GROUP_NOT_MEMBER) {
318 	    syslog (LOG_ERR | LOG_AUTH, "%s to %s: not in group %s",
319 		    login->pw_name, su->pw_name, ROOT_GROUP);
320 	    return 1;
321 	}
322 	return 0;
323     }
324     return 0;
325 }
326 
327 int
328 main(int argc, char **argv)
329 {
330     int i, optind = 0;
331     char *su_user;
332     struct passwd *su_info;
333     struct passwd *login_info;
334 
335     struct passwd *pwd;
336 
337     char *shell;
338 
339     int ok = 0;
340 
341     setprogname (argv[0]);
342 
343     if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optind))
344 	usage(1);
345 
346     for (i=0; i < optind; i++)
347       if (strcmp(argv[i], "-") == 0) {
348 	 full_login = 1;
349 	 break;
350       }
351 
352     if(help_flag)
353 	usage(0);
354     if(version_flag) {
355 	print_version(NULL);
356 	exit(0);
357     }
358     if(optind >= argc)
359 	su_user = "root";
360     else
361 	su_user = argv[optind++];
362 
363     if (!issuid() && getuid() != 0)
364 	warnx("Not setuid and you are not root, expect this to fail");
365 
366     pwd = k_getpwnam(su_user);
367     if(pwd == NULL)
368 	errx (1, "unknown login %s", su_user);
369     if (pwd->pw_uid == 0 && strcmp ("root", su_user) != 0) {
370 	syslog (LOG_ALERT, "NIS attack, user %s has uid 0", su_user);
371 	errx (1, "unknown login %s", su_user);
372     }
373     su_info = dup_info(pwd);
374     if (su_info == NULL)
375 	errx (1, "malloc: out of memory");
376 
377 	pwd = getpwuid(getuid());
378     if(pwd == NULL)
379 	errx(1, "who are you?");
380     login_info = dup_info(pwd);
381     if (login_info == NULL)
382 	errx (1, "malloc: out of memory");
383     if(env_flag)
384 	shell = login_info->pw_shell;
385     else
386 	shell = su_info->pw_shell;
387     if(shell == NULL || *shell == '\0')
388 	shell = _PATH_BSHELL;
389 
390 
391 #ifdef KRB5
392     if(kerberos_flag && ok == 0 &&
393        krb5_verify(login_info, su_info, kerberos_instance) == 0)
394 	ok = 5;
395 #endif
396 
397     if(ok == 0 && login_info->pw_uid && verify_unix(login_info, su_info) != 0) {
398 	printf("Sorry!\n");
399 	exit(1);
400     }
401 
402 #ifdef HAVE_GETSPNAM
403    {  struct spwd *sp;
404       long    today;
405 
406     sp = getspnam(su_info->pw_name);
407     if (sp != NULL) {
408 	today = time(0)/(24L * 60 * 60);
409 	if (sp->sp_expire > 0) {
410 	    if (today >= sp->sp_expire) {
411 		if (login_info->pw_uid)
412 		    errx(1,"Your account has expired.");
413 		else
414 		    printf("Your account has expired.");
415             }
416             else if (sp->sp_expire - today < 14)
417                 printf("Your account will expire in %d days.\n",
418 		       (int)(sp->sp_expire - today));
419 	}
420 	if (sp->sp_max > 0) {
421 	    if (today >= sp->sp_lstchg + sp->sp_max) {
422 		if (login_info->pw_uid)
423 		    errx(1,"Your password has expired. Choose a new one.");
424 		else
425 		    printf("Your password has expired. Choose a new one.");
426 	    }
427 	    else if (today >= sp->sp_lstchg + sp->sp_max - sp->sp_warn)
428 		printf("Your account will expire in %d days.\n",
429 		       (int)(sp->sp_lstchg + sp->sp_max -today));
430 	}
431     }
432     }
433 #endif
434     {
435 	char *tty = ttyname (STDERR_FILENO);
436 	syslog (LOG_NOTICE | LOG_AUTH, tty ? "%s to %s on %s" : "%s to %s",
437 		login_info->pw_name, su_info->pw_name, tty);
438     }
439 
440 
441     if(!env_flag) {
442 	if(full_login) {
443 	    char *t = getenv ("TERM");
444 	    char **newenv = NULL;
445 	    int i, j;
446 
447 	    i = read_environment(_PATH_ETC_ENVIRONMENT, &newenv);
448 
449 	    environ = malloc ((10 + i) * sizeof (char *));
450 	    if (environ == NULL)
451 		err (1, "malloc");
452 	    environ[0] = NULL;
453 
454 	    for (j = 0; j < i; j++) {
455 		char *p = strchr(newenv[j], '=');
456 		if (p == NULL)
457 		    errx(1, "enviroment '%s' missing '='", newenv[j]);
458 		*p++ = 0;
459 		esetenv (newenv[j], p, 1);
460 	    }
461 	    free(newenv);
462 
463 	    esetenv ("PATH", _PATH_DEFPATH, 1);
464 	    if (t)
465 		esetenv ("TERM", t, 1);
466 	    if (chdir (su_info->pw_dir) < 0)
467 		errx (1, "no directory");
468 	}
469 	if (full_login || su_info->pw_uid)
470 	    esetenv ("USER", su_info->pw_name, 1);
471 	esetenv("HOME", su_info->pw_dir, 1);
472 	esetenv("SHELL", shell, 1);
473     }
474 
475     {
476 	int i;
477 	char **args;
478 	char *p;
479 
480 	p = strrchr(shell, '/');
481 	if(p)
482 	    p++;
483 	else
484 	    p = shell;
485 
486 	if (strcmp(p, "csh") != 0)
487 	    csh_f_flag = 0;
488 
489         args = malloc(((cmd ? 2 : 0) + 1 + argc - optind + 1 + csh_f_flag) * sizeof(*args));
490 	if (args == NULL)
491 	    err (1, "malloc");
492 	i = 0;
493 	if(full_login) {
494 	    if (asprintf(&args[i++], "-%s", p) == -1)
495 		errx (1, "malloc");
496 	} else
497 	    args[i++] = p;
498 	if (cmd) {
499 	   args[i++] = "-c";
500 	   args[i++] = cmd;
501 	}
502 
503 	if (csh_f_flag)
504 	    args[i++] = "-f";
505 
506 	for (argv += optind; *argv; ++argv)
507 	   args[i++] = *argv;
508 	args[i] = NULL;
509 
510 	if(setgid(su_info->pw_gid) < 0)
511 	    err(1, "setgid");
512 	if (initgroups (su_info->pw_name, su_info->pw_gid) < 0)
513 	    err (1, "initgroups");
514 	if(setuid(su_info->pw_uid) < 0
515 	   || (su_info->pw_uid != 0 && setuid(0) == 0))
516 	    err(1, "setuid");
517 
518 #ifdef KRB5
519         if (ok == 5)
520            krb5_start_session();
521 #endif
522 	execve(shell, args, environ);
523     }
524 
525     exit(1);
526 }
527