/* * chsh.c -- change your login shell * (c) 1994 by salvatore valente * (c) 2012 by Cody Maloney * * 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 * * 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 * - added Native Language Support */ #include #include #include #include #include #include #include #include #include #include #include "c.h" #include "env.h" #include "closestream.h" #include "islocal.h" #include "nls.h" #include "pathnames.h" #include "setpwnam.h" #include "xalloc.h" #include "ch-common.h" #ifdef HAVE_LIBSELINUX # include # include # include "selinux_utils.h" #endif #ifdef HAVE_LIBUSER # include # include "libuser.h" #elif CHFN_CHSH_PASSWORD # include "auth.h" #endif struct sinfo { char *username; char *shell; }; static void __attribute__((__noreturn__)) usage (FILE *fp) { fputs(USAGE_HEADER, fp); fprintf(fp, _(" %s [options] []\n"), program_invocation_short_name); fputs(USAGE_OPTIONS, fp); fputs(_(" -s, --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); } /* * 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; char *buf = NULL; size_t sz = 0, len; found = false; fp = fopen(_PATH_SHELLS, "r"); if (!fp) { if (!shell_name) warnx(_("No known shells.")); return true; } while (getline(&buf, &sz, fp) != -1) { len = strlen(buf); /* ignore comments */ if (*buf == '#') continue; /* skip blank lines*/ if (len < 2) continue; /* strip the ending newline */ if (buf[len - 1] == '\n') buf[len - 1] = 0; /* check or output the shell */ if (shell_name) { if (!strcmp(shell_name, buf)) { found = true; break; } } else printf("%s\n", buf); } fclose(fp); free(buf); return found; } /* * 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) { 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; } if (illegal_passwd_chars(shell)) { warnx(_("%s: has illegal characters"), shell); return -1; } #ifdef ONLY_LISTED_SHELLS if (!get_shell_list(shell)) { if (!getuid()) warnx(_ ("Warning: \"%s\" is not listed in %s."), shell, _PATH_SHELLS); else errx(EXIT_FAILURE, _("\"%s\" is not listed in %s.\n" "Use %s -l to see list."), shell, _PATH_SHELLS, program_invocation_short_name); } #else if (!get_shell_list(shell)) { warnx(_("\"%s\" is not listed in %s.\n" "Use %s -l to see list."), shell, _PATH_SHELLS, program_invocation_short_name); } #endif return 0; } 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); atexit(close_stdout); 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); } #ifndef HAVE_LIBUSER if (!(is_local(pw->pw_name))) errx(EXIT_FAILURE, _("can only change local entries")); #endif #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(_PATH_PASSWD) != 0) errx(EXIT_FAILURE, _("can't set default context for %s"), _PATH_PASSWD); } #endif oldshell = pw->pw_shell; if (oldshell == NULL || *oldshell == '\0') oldshell = _PATH_BSHELL; /* default */ /* reality check */ #ifdef HAVE_LIBUSER /* If we're setuid and not really root, disallow the password change. */ if (geteuid() != getuid() && uid != pw->pw_uid) { #else if (uid != 0 && uid != pw->pw_uid) { #endif 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 %s, " "shell change denied"), _PATH_SHELLS); } shell = info.shell; printf(_("Changing shell for %s.\n"), pw->pw_name); #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) if(!auth_pam("chsh", uid, pw->pw_name)) { return EXIT_FAILURE; } #endif if (!shell) { shell = prompt(_("New shell"), oldshell); if (!shell) return EXIT_SUCCESS; } if (check_shell(shell) < 0) return EXIT_FAILURE; if (strcmp(oldshell, shell) == 0) errx(EXIT_SUCCESS, _("Shell not changed.")); #ifdef HAVE_LIBUSER if (set_value_libuser("chsh", pw->pw_name, uid, LU_LOGINSHELL, shell) < 0) errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later.")); #else pw->pw_shell = shell; if (setpwnam(pw) < 0) err(EXIT_FAILURE, _("setpwnam failed\n" "Shell *NOT* changed. Try again later.")); #endif printf(_("Shell changed.\n")); return EXIT_SUCCESS; }