Logo Search packages:      
Sourcecode: util-linux version File versions  Download package

chsh.c

/*
 *   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
 *
 *
 */

#if 0
#define _POSIX_SOURCE 1
#endif

#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 "my_crypt.h"
#include "islocal.h"
#include "setpwnam.h"
#include "nls.h"
#include "env.h"

#if REQUIRE_PASSWORD && USE_PAM
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#endif

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

typedef unsigned char boolean;
#define false 0
#define true 1

/* Only root is allowed to assign a luser a non-listed shell, by default */
#define ONLY_LISTED_SHELLS 1


static char *whoami;

static char buf[FILENAME_MAX];

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

static void parse_argv (int argc, char *argv[], struct sinfo *pinfo);
static void usage (FILE *fp);
static char *prompt (char *question, char *def_val);
static int check_shell (char *shell);
static boolean get_shell_list (char *shell);
static void *xmalloc (int bytes);

#define memzero(ptr, size) memset((char *) ptr, 0, size)

int
main (int argc, char *argv[]) {
    char *cp, *shell, *oldshell;
    uid_t uid;
    struct sinfo info;
    struct passwd *pw;
#if REQUIRE_PASSWORD && USE_PAM
    pam_handle_t *pamh = NULL;
    int retcode;
    struct pam_conv conv = { misc_conv, NULL };
#endif

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

    /* whoami is the program name for error messages */
    whoami = argv[0];
    if (! whoami) whoami = "chsh";
    for (cp = whoami; *cp; cp++)
      if (*cp == '/') whoami = cp + 1;

    uid = getuid ();
    memzero (&info, sizeof (info));

    parse_argv (argc, argv, &info);
    pw = NULL;
    if (! info.username) {
      pw = getpwuid (uid);
      if (! pw) {
          fprintf (stderr, _("%s: you (user %d) don't exist.\n"), whoami, uid);
          return (-1); }
    }
    else {
      pw = getpwnam (info.username);
      if (! pw) {
          cp = info.username;
          fprintf (stderr, _("%s: user \"%s\" does not exist.\n"), whoami, cp);
          return (-1); }
    }

    if (!(is_local(pw->pw_name))) {
       fprintf (stderr, _("%s: can only change local entries; use yp%s instead.\n"),
           whoami, whoami);
       exit(1);
    }

#ifdef WITH_SELINUX
    if (is_selinux_enabled()) {
      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) strdup(_("Unknown user context"));
        fprintf(stderr, _("%s: %s is not authorized to change the shell of %s\n"),
              whoami, user_context, pw->pw_name);
        freecon(user_context);
        exit(1);
      }
      }
      if (setupDefaultContext("/etc/passwd") != 0) {
      fprintf(stderr,_("%s: Can't set default context for /etc/passwd"),
            whoami);
      exit(1);
      }
    }
#endif

    oldshell = pw->pw_shell;
    if (!oldshell[0]) oldshell = "/bin/sh";

    /* reality check */
    if (uid != 0 && uid != pw->pw_uid) {
      errno = EACCES;
      fprintf(stderr,_("%s: Running UID doesn't match UID of user we're "
                   "altering, shell change denied\n"), whoami);
      return (-1);
    }
    if (uid != 0 && !get_shell_list(oldshell)) {
      errno = EACCES;
      fprintf(stderr,_("%s: Your shell is not in /etc/shells, shell change"
            " denied\n"),whoami);
      return (-1);
    }
    
    shell = info.shell;

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

#if REQUIRE_PASSWORD
# if USE_PAM
    if(uid != 0) {
        if (pam_start("chsh", pw->pw_name, &conv, &pamh)) {
          puts(_("Password error."));
          exit(1);
      }
        if (pam_authenticate(pamh, 0)) {
          puts(_("Password error."));
          exit(1);
      }
        retcode = pam_acct_mgmt(pamh, 0);
        if (retcode == PAM_NEW_AUTHTOK_REQD)
          retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
        if (retcode) {
          puts(_("Password error."));
          exit(1);
      }
        if (pam_setcred(pamh, 0)) {
          puts(_("Password error."));
          exit(1);
      }
        /* no need to establish a session; this isn't a session-oriented
         * activity... */
    }
# else /* USE_PAM */
    /* require password, unless root */
    if(uid != 0 && pw->pw_passwd && pw->pw_passwd[0]) {
      char *pwdstr = getpass(_("Password: "));
      if(strncmp(pw->pw_passwd,
               crypt(pwdstr, pw->pw_passwd), 13)) {
          puts(_("Incorrect password."));
          exit(1);
      }
    }
# endif /* USE_PAM */
#endif /* REQUIRE_PASSWORD */

    if (! shell) {
      shell = prompt (_("New shell"), oldshell);
      if (! shell) return 0;
    }
    
    if (check_shell (shell) < 0) return (-1);

    if (! strcmp (pw->pw_shell, shell)) {
      printf (_("Shell not changed.\n"));
      return 0;
    }
    if (!strcmp(shell, "/bin/sh")) shell = "";
    pw->pw_shell = shell;
    if (setpwnam (pw) < 0) {
      perror ("setpwnam");
      printf( _("Shell *NOT* changed.  Try again later.\n") );
      return (-1);
    }
    printf (_("Shell changed.\n"));
    return 0;
}

/*
 *  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 ("%s\n", util_linux_version);
          exit (0);
      case 'u':
          usage (stdout);
          exit (0);
      case 'l':
          get_shell_list (NULL);
          exit (0);
      case 's':
          if (! optarg) {
            usage (stderr);
            exit (-1);
          }
          pinfo->shell = optarg;
          break;
      default:
          usage (stderr);
          exit (-1);
      }
    }
    /* done parsing arguments.      check for a username. */
    if (optind < argc) {
      if (optind + 1 < argc) {
          usage (stderr);
          exit (-1);
      }
      pinfo->username = argv[optind];
    }
}

/*
 *  usage () --
 *    print out a usage message.
 */
static void
usage (FILE *fp) {
    fprintf (fp,
           _("Usage: %s [ -s shell ] [ --list-shells ] "
             "[ --help ] [ --version ]\n"
             "       [ username ]\n"), whoami);
}

/*
 *  prompt () --
 *    ask the user for a given field and return it.
 */
static char *
prompt (char *question, char *def_val) {
    int len;
    char *ans, *cp;
  
    if (! def_val) def_val = "";
    printf("%s [%s]: ", question, def_val);
    *buf = 0;
    if (fgets (buf, sizeof (buf), stdin) == NULL) {
      printf (_("\nAborted.\n"));
      exit (-1);
    }
    /* 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) {
    int i, c;

    if (*shell != '/') {
      printf (_("%s: shell must be a full path name.\n"), whoami);
      return (-1);
    }
    if (access (shell, F_OK) < 0) {
      printf (_("%s: \"%s\" does not exist.\n"), whoami, shell);
      return (-1);
    }
    if (access (shell, X_OK) < 0) {
      printf (_("%s: \"%s\" is not executable.\n"), whoami, shell);
      return (-1);
    }
    /* keep /etc/passwd clean. */
    for (i = 0; i < strlen (shell); i++) {
      c = shell[i];
      if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
          printf (_("%s: '%c' is not allowed.\n"), whoami, c);
          return (-1);
      }
      if (iscntrl (c)) {
          printf (_("%s: Control characters are not allowed.\n"), whoami);
          return (-1);
      }
    }
#if ONLY_LISTED_SHELLS
    if (! get_shell_list (shell)) {
       if (!getuid())
        printf (_("Warning: \"%s\" is not listed in /etc/shells\n"), shell);
       else {
        printf (_("%s: \"%s\" is not listed in /etc/shells.\n"),
              whoami, shell);
        printf( _("%s: use -l option to see list\n"), whoami );
        exit(1);
       }
    }
#else
    if (! get_shell_list (shell)) {
       printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
       printf( _("Use %s -l to see list.\n"), whoami );
    }
#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 boolean
get_shell_list (char *shell_name) {
    FILE *fp;
    boolean found;
    int len;

    found = false;
    fp = fopen ("/etc/shells", "r");
    if (! fp) {
      if (! shell_name) printf (_("No known shells.\n"));
      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;
}

/*
 *  xmalloc () -- malloc that never fails.
 */
static void *
xmalloc (int bytes) {
    void *vp;

    vp = malloc (bytes);
    if (! vp && bytes > 0) {
      perror (_("malloc failed"));
      exit (-1);
    }
    return vp;
}

Generated by  Doxygen 1.6.0   Back to index