summaryrefslogblamecommitdiffstats
path: root/login-utils/chsh.c
blob: f6a5c9fa19a0821316ab346f2eaf6af4ccaca5c5 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                 
                    

                                 

                                                                     





                                                                              
                                                        


                                    
   








                      
                    
 
                    
              

                     

                
                      
                   
 
                      




                                   




                   
                                                                     

                                                    
                                        
 
                                                          
 








                                                                                

                                                     
 

                               
                           


                      
 




                                       
                    
                                   




                                   
                 
                                                                     


                                      
                 

                                                                

     
                                 
                                                               
 
                      
                                   



                                                       

                                                     

                                                                                 
                               

         
                                                  
                                                                           


      
                            

                                                                     
 
                       

                                        

                                                                          

                                                
                       

                                                                
     
 
                       
 
                                                         
 
                       
                   




                                                               

                                          

                                            

                                          


                                            
                                                                      

                                          

                                       

                                          



                                                                        
     
                             
 
                  
                                                  

                                
     
 

                                
 

                                                     

                            

                                                          
                            
     
                                   
                        






                                                                      

                                                          













                                                                    
                

                  
                                        
                                

                           

                                  
                                
                 
                         
                               



                                  



                                                        
                              
                           



                                       




                                                     

                                        

                   
                     
 


                                           

                                                 







                                                  
                     







                                                               

                           
                      
 
               
                  
 
                        
                                                    
                  

                                   
                                                  
                  

                                   
                                                      
                  




                                                                        
                                                
                      

                          
                                                            
                      

         
                         

                                   



                                                                                                


                                   

                                                                                

      







                                                                       
          
                                   
             
              
            
                       



                                    

                                          





















                                                   
/*
 *   chsh.c -- change your login shell
 *   (c) 1994 by salvatore valente <svalente@athena.mit.edu>
 *
 *   this program is free software.  you can redistribute it and
 *   modify it under the terms of the gnu general public license.
 *   there is no warranty.
 *
 *   $Author: aebr $
 *   $Revision: 1.19 $
 *   $Date: 1998/06/11 22:30:14 $
 *
 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
 *   patches from Zefram <A.Main@dcs.warwick.ac.uk>
 *
 * Updated Mon Jul  1 18:46:22 1996 by janl@math.uio.no with security
 *   suggestion from Zefram.  Disallowing users with shells not in /etc/shells
 *   from changing their shell.
 *
 *   1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
 *   - added Native Language Support
 *
 *
 */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <ctype.h>
#include <getopt.h>
#include <stdbool.h>

#include "pamfail.h"
#include "c.h"
#include "islocal.h"
#include "setpwnam.h"
#include "nls.h"
#include "env.h"
#include "pathnames.h"
#include "xalloc.h"

#ifdef HAVE_LIBSELINUX
#include <selinux/selinux.h>
#include <selinux/av_permissions.h>
#include "selinux_utils.h"
#endif

struct sinfo {
    char *username;
    char *shell;
};

static void parse_argv (int argc, char *argv[], struct sinfo *pinfo);
static char *prompt (char *question, char *def_val);
static int check_shell (char *shell);
static int get_shell_list (char *shell);

static void __attribute__((__noreturn__)) usage (FILE *fp)
{
    fputs(USAGE_HEADER, fp);
    fprintf(fp, _(" %s [options] [username]\n"), program_invocation_short_name);
    fputs(USAGE_OPTIONS, fp);
    fputs(_(" -s, --shell <shell>  specify login shell\n"), fp);
    fputs(_(" -l, --list-shells    print list of shells and exit\n"), fp);
    fputs(USAGE_SEPARATOR, fp);
    fputs(_(" -u, --help     display this help and exit\n"), fp);
    fputs(_(" -v, --version  output version information and exit\n"), fp);
    fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
    exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}

int
main (int argc, char *argv[]) {
    char *shell, *oldshell;
    uid_t uid;
    struct sinfo info;
    struct passwd *pw;

    sanitize_env();
    setlocale(LC_ALL, "");
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);

    uid = getuid ();
    memset(&info, 0, sizeof(info));

    parse_argv (argc, argv, &info);
    pw = NULL;
    if (! info.username) {
	pw = getpwuid (uid);
	if (! pw)
	    errx(EXIT_FAILURE, _("you (user %d) don't exist."), uid);
    }
    else {
	pw = getpwnam (info.username);
	if (! pw)
	    errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
			    info.username);
    }

    if (!(is_local(pw->pw_name)))
       errx(EXIT_FAILURE, _("can only change local entries."));

#ifdef HAVE_LIBSELINUX
    if (is_selinux_enabled() > 0) {
      if(uid == 0) {
	if (checkAccess(pw->pw_name,PASSWD__CHSH)!=0) {
	  security_context_t user_context;
	  if (getprevcon(&user_context) < 0)
	    user_context = (security_context_t) NULL;

	  errx(EXIT_FAILURE, _("%s is not authorized to change the shell of %s"),
		  user_context ? : _("Unknown user context"),
		  pw->pw_name);
	}
      }
      if (setupDefaultContext("/etc/passwd") != 0)
	errx(EXIT_FAILURE, _("can't set default context for /etc/passwd"));
    }
#endif

    oldshell = pw->pw_shell;
    if (oldshell == NULL || *oldshell == '\0')
	    oldshell = _PATH_BSHELL;			/* default */

    /* reality check */
    if (uid != 0 && uid != pw->pw_uid) {
	errno = EACCES;
	err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
			 "altering, shell change denied"));
    }
    if (uid != 0 && !get_shell_list(oldshell)) {
	errno = EACCES;
	err(EXIT_FAILURE, _("your shell is not in /etc/shells, "
			    "shell change denied"));
    }

    shell = info.shell;

    printf( _("Changing shell for %s.\n"), pw->pw_name );

#ifdef REQUIRE_PASSWORD
    if (uid != 0) {
	pam_handle_t *pamh = NULL;
	struct pam_conv conv = { misc_conv, NULL };
	int retcode;

	retcode = pam_start("chsh", pw->pw_name, &conv, &pamh);
	if (pam_fail_check(pamh, retcode))
	    exit(EXIT_FAILURE);

	retcode = pam_authenticate(pamh, 0);
	if (pam_fail_check(pamh, retcode))
	    exit(EXIT_FAILURE);

	retcode = pam_acct_mgmt(pamh, 0);
	if (retcode == PAM_NEW_AUTHTOK_REQD)
	    retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
	if (pam_fail_check(pamh, retcode))
	    exit(EXIT_FAILURE);

	retcode = pam_setcred(pamh, 0);
	if (pam_fail_check(pamh, retcode))
	    exit(EXIT_FAILURE);

	pam_end(pamh, 0);
	/* no need to establish a session; this isn't a session-oriented
	 * activity... */
    }
#endif /* REQUIRE_PASSWORD */

    if (! shell) {
	shell = prompt (_("New shell"), oldshell);
	if (! shell)
	    return EXIT_SUCCESS;
    }

    if (check_shell (shell) < 0)
        return EXIT_FAILURE;

    if (! strcmp (pw->pw_shell, shell))
	errx (EXIT_SUCCESS, _("Shell not changed."));
    pw->pw_shell = shell;
    if (setpwnam (pw) < 0) {
	warn(_("setpwnam failed\n"
	       "Shell *NOT* changed.  Try again later."));
	return EXIT_FAILURE;
    }
    printf (_("Shell changed.\n"));
    return EXIT_SUCCESS;
}

/*
 *  parse_argv () --
 *	parse the command line arguments, and fill in "pinfo" with any
 *	information from the command line.
 */
static void
parse_argv (int argc, char *argv[], struct sinfo *pinfo) {
    int index, c;

    static struct option long_options[] = {
	{ "shell",	 required_argument, 0, 's' },
	{ "list-shells", no_argument,	    0, 'l' },
	{ "help",	 no_argument,	    0, 'u' },
	{ "version",	 no_argument,	    0, 'v' },
	{ NULL,		 no_argument,	    0, '0' },
    };

    optind = c = 0;
    while (c != EOF) {
	c = getopt_long (argc, argv, "s:luv", long_options, &index);
	switch (c) {
	case -1:
	    break;
	case 'v':
	    printf (UTIL_LINUX_VERSION);
	    exit (EXIT_SUCCESS);
	case 'u':
	    usage (stdout);
	case 'l':
	    get_shell_list (NULL);
	    exit (EXIT_SUCCESS);
	case 's':
	    if (! optarg)
		usage (stderr);
	    pinfo->shell = optarg;
	    break;
	default:
	    usage (stderr);
	}
    }
    /* done parsing arguments.	check for a username. */
    if (optind < argc) {
	if (optind + 1 < argc)
	    usage (stderr);
	pinfo->username = argv[optind];
    }
}


/*
 *  prompt () --
 *	ask the user for a given field and return it.
 */
static char *
prompt (char *question, char *def_val) {
    int len;
    char *ans, *cp;
    char buf[BUFSIZ];

    if (! def_val) def_val = "";
    printf("%s [%s]: ", question, def_val);
    *buf = 0;
    if (fgets (buf, sizeof (buf), stdin) == NULL)
	errx (EXIT_FAILURE, _("Aborted."));
    /* remove the newline at the end of buf. */
    ans = buf;
    while (isspace (*ans)) ans++;
    len = strlen (ans);
    while (len > 0 && isspace (ans[len-1])) len--;
    if (len <= 0) return NULL;
    ans[len] = 0;
    cp = (char *) xmalloc (len + 1);
    strcpy (cp, ans);
    return cp;
}

/*
 *  check_shell () -- if the shell is completely invalid, print
 *	an error and return (-1).
 *	if the shell is a bad idea, print a warning.
 */
static int
check_shell (char *shell) {
    unsigned int i, c;

    if (!shell)
	return -1;

    if (*shell != '/') {
	warnx (_("shell must be a full path name"));
	return -1;
    }
    if (access (shell, F_OK) < 0) {
	warnx (_("\"%s\" does not exist"), shell);
	return -1;
    }
    if (access (shell, X_OK) < 0) {
	printf (_("\"%s\" is not executable"), shell);
	return -1;
    }
    /* keep /etc/passwd clean. */
    for (i = 0; i < strlen (shell); i++) {
	c = shell[i];
	if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
	    warnx (_("'%c' is not allowed"), c);
	    return -1;
	}
	if (iscntrl (c)) {
	    warnx (_("control characters are not allowed"));
	    return -1;
	}
    }
#ifdef ONLY_LISTED_SHELLS
    if (! get_shell_list (shell)) {
       if (!getuid())
	  warnx(_("Warning: \"%s\" is not listed in /etc/shells."), shell);
       else
	  errx(EXIT_FAILURE, _("\"%s\" is not listed in /etc/shells.\n"
	                       "Use %s -l to see list."), shell, program_invocation_short_name);
    }
#else
    if (! get_shell_list (shell)) {
       warnx(_("\"%s\" is not listed in /etc/shells.\n"
               "Use %s -l to see list."), shell, program_invocation_short_name);
    }
#endif
    return 0;
}

/*
 *  get_shell_list () -- if the given shell appears in /etc/shells,
 *	return true.  if not, return false.
 *	if the given shell is NULL, /etc/shells is outputted to stdout.
 */
static int
get_shell_list (char *shell_name) {
    FILE *fp;
    int found;
    int len;
    char buf[PATH_MAX];

    found = false;
    fp = fopen ("/etc/shells", "r");
    if (! fp) {
	if (! shell_name)
	    warnx (_("No known shells."));
	return true;
    }
    while (fgets (buf, sizeof (buf), fp) != NULL) {
	/* ignore comments */
	if (*buf == '#') continue;
	len = strlen (buf);
	/* strip the ending newline */
	if (buf[len - 1] == '\n') buf[len - 1] = 0;
	/* ignore lines that are too damn long */
	else continue;
	/* check or output the shell */
	if (shell_name) {
	    if (! strcmp (shell_name, buf)) {
		found = true;
		break;
	    }
	}
	else printf ("%s\n", buf);
    }
    fclose (fp);
    return found;
}