| /* |
| * 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" |
| #include "pathnames.h" |
| |
| #if defined(REQUIRE_PASSWORD) && defined(HAVE_SECURITY_PAM_MISC_H) |
| #include <security/pam_appl.h> |
| #include <security/pam_misc.h> |
| |
| #define PAM_FAIL_CHECK(_ph, _rc) \ |
| do { \ |
| if ((_rc) != PAM_SUCCESS) { \ |
| fprintf(stderr, "\n%s\n", pam_strerror((_ph), (_rc))); \ |
| pam_end((_ph), (_rc)); \ |
| exit(1); \ |
| } \ |
| } while(0) |
| |
| #endif /* PAM */ |
| |
| #ifdef HAVE_LIBSELINUX |
| #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; |
| |
| 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 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) 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 == NULL || *oldshell == '\0') |
| oldshell = _PATH_BSHELL; /* default */ |
| |
| /* 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 ); |
| |
| #ifdef REQUIRE_PASSWORD |
| #ifdef HAVE_SECURITY_PAM_MISC_H |
| 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(retcode != PAM_SUCCESS) { |
| fprintf(stderr, _("%s: PAM failure, aborting: %s\n"), |
| whoami, pam_strerror(pamh, retcode)); |
| exit(1); |
| } |
| |
| retcode = pam_authenticate(pamh, 0); |
| PAM_FAIL_CHECK(pamh, retcode); |
| |
| retcode = pam_acct_mgmt(pamh, 0); |
| if (retcode == PAM_NEW_AUTHTOK_REQD) |
| retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); |
| PAM_FAIL_CHECK(pamh, retcode); |
| |
| retcode = pam_setcred(pamh, 0); |
| PAM_FAIL_CHECK(pamh, retcode); |
| |
| pam_end(pamh, 0); |
| /* no need to establish a session; this isn't a session-oriented |
| * activity... */ |
| } |
| #else /* HAVE_SECURITY_PAM_MISC_H */ |
| /* 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 /* HAVE_SECURITY_PAM_MISC_H */ |
| #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; |
| } |
| 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", PACKAGE_STRING); |
| 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) |
| return (-1); |
| |
| 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); |
| } |
| } |
| #ifdef 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; |
| } |