fix type for strerror_r
[heimdal.git] / appl / su / su.c
blobe61492972f0f2262c08c59654ae734eadd37d7c8
1 /*
2 * Copyright (c) 1999 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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. */
33 #include <config.h>
35 RCSID("$Id$");
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
41 #include <syslog.h>
43 #ifdef HAVE_PATHS_H
44 #include <paths.h>
45 #endif
47 #ifdef HAVE_SHADOW_H
48 #include <shadow.h>
49 #endif
51 #include <pwd.h>
52 #ifdef HAVE_CRYPT_H
53 #include <crypt.h>
54 #endif
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>
65 #include "supaths.h"
67 #if !HAVE_DECL_ENVIRON
68 extern char **environ;
69 #endif
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];
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 },
99 static void
100 usage (int ret)
102 arg_printusage (args,
103 sizeof(args)/sizeof(*args),
104 NULL,
105 "[login [shell arguments]]");
106 exit (ret);
109 static void
110 free_info(struct passwd *p)
112 free (p->pw_name);
113 free (p->pw_passwd);
114 free (p->pw_dir);
115 free (p->pw_shell);
116 free (p);
119 static struct passwd*
120 dup_info(const struct passwd *pwd)
122 struct passwd *info;
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;
138 return info;
141 #ifdef KRB5
142 static krb5_context context;
143 static krb5_ccache ccache;
145 static int
146 krb5_verify(const struct passwd *login_info,
147 const struct passwd *su_info,
148 const char *kerberos_instance)
150 krb5_error_code ret;
151 krb5_principal p;
152 krb5_realm *realms, *r;
153 char *login_name = NULL;
154 int user_ok = 0;
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;
167 ret = krb5_get_default_realms(context, &realms);
168 if (ret)
169 return 1;
171 /* Check all local realms */
172 for (r = realms; *r != NULL && !user_ok; r++) {
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;
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;
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;
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;
220 krb5_free_host_realm(context, realms);
221 if (!user_ok)
222 return 1;
223 return 0;
226 static int
227 krb5_start_session(void)
229 krb5_ccache ccache2;
230 char *cc_name;
231 int ret;
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;
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;
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");
253 esetenv("KRB5CCNAME", cc_name, 1);
255 /* convert creds? */
256 if(k_hasafs()) {
257 if (k_setpag() == 0)
258 krb5_afslog(context, ccache2, NULL, NULL);
261 krb5_cc_close(context, ccache2);
262 krb5_cc_destroy(context, ccache);
263 return 0;
265 #endif
268 #define GROUP_MEMBER 0
269 #define GROUP_MISSING 1
270 #define GROUP_EMPTY 2
271 #define GROUP_NOT_MEMBER 3
273 static int
274 group_member_p(const char *group, const char *user)
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;
289 static int
290 verify_unix(struct passwd *login, struct passwd *su)
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;
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;
322 return 0;
324 return 0;
328 main(int argc, char **argv)
330 int i, optind = 0;
331 char *su_user;
332 struct passwd *su_info;
333 struct passwd *login_info;
335 struct passwd *pwd;
337 char *shell;
339 int ok = 0;
341 setprogname (argv[0]);
343 if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optind))
344 usage(1);
346 for (i=0; i < optind; i++)
347 if (strcmp(argv[i], "-") == 0) {
348 full_login = 1;
349 break;
352 if(help_flag)
353 usage(0);
354 if(version_flag) {
355 print_version(NULL);
356 exit(0);
358 if(optind >= argc)
359 su_user = "root";
360 else
361 su_user = argv[optind++];
363 if (!issuid() && getuid() != 0)
364 warnx("Not setuid and you are not root, expect this to fail");
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);
373 su_info = dup_info(pwd);
374 if (su_info == NULL)
375 errx (1, "malloc: out of memory");
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;
391 #ifdef KRB5
392 if(kerberos_flag && ok == 0 &&
393 krb5_verify(login_info, su_info, kerberos_instance) == 0)
394 ok = 5;
395 #endif
397 if(ok == 0 && login_info->pw_uid && verify_unix(login_info, su_info) != 0) {
398 printf("Sorry!\n");
399 exit(1);
402 #ifdef HAVE_GETSPNAM
403 { struct spwd *sp;
404 long today;
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.");
416 else if (sp->sp_expire - today < 14)
417 printf("Your account will expire in %d days.\n",
418 (int)(sp->sp_expire - today));
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.");
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));
433 #endif
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);
441 if(!env_flag) {
442 if(full_login) {
443 char *t = getenv ("TERM");
444 char **newenv = NULL;
445 int i, j;
447 i = read_environment(_PATH_ETC_ENVIRONMENT, &newenv);
449 environ = malloc ((10 + i) * sizeof (char *));
450 if (environ == NULL)
451 err (1, "malloc");
452 environ[0] = NULL;
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);
461 free(newenv);
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");
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);
476 int i;
477 char **args;
478 char *p;
480 p = strrchr(shell, '/');
481 if(p)
482 p++;
483 else
484 p = shell;
486 if (strcmp(p, "csh") != 0)
487 csh_f_flag = 0;
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;
503 if (csh_f_flag)
504 args[i++] = "-f";
506 for (argv += optind; *argv; ++argv)
507 args[i++] = *argv;
508 args[i] = NULL;
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");
518 #ifdef KRB5
519 if (ok == 5)
520 krb5_start_session();
521 #endif
522 execve(shell, args, environ);
525 exit(1);