/ appl / su / su.c
su.c
  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  }