home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright 1989, 1990, John F. Haugh II
- * All rights reserved.
- *
- * Permission is granted to copy and create derivative works for any
- * non-commercial purpose, provided this copyright notice is preserved
- * in all copies of source code, or included in human readable form
- * and conspicuously displayed on all copies of object code or
- * distribution media.
- */
-
- #include <sys/types.h>
- #include <syslog.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <signal.h>
-
- #ifndef lint
- static char sccsid[] = "@(#)chsh.c 3.3 11:23:29 12/19/90";
- #endif
-
- /*
- * Set up some BSD defines so that all the BSD ifdef's are
- * kept right here
- */
-
- #ifndef BSD
- #include <string.h>
- #include <memory.h>
- #else
- #include <strings.h>
- #define strchr index
- #define strrchr rindex
- #endif
-
- #include "config.h"
- #include "pwd.h"
-
- /*
- * Global variables.
- */
-
- char *Progname; /* Program name */
- int amroot; /* Real UID is root */
- char loginsh[BUFSIZ]; /* Name of new login shell */
-
- /*
- * External identifiers
- */
-
- extern struct passwd *getpwuid ();
- extern struct passwd *getpwnam ();
- extern int optind;
- extern char *optarg;
- extern char *getlogin ();
- #ifdef NDBM
- extern int pw_dbm_mode;
- #endif
-
- /*
- * #defines for messages. This facilities foreign language conversion
- * since all messages are defined right here.
- */
-
- #define USAGE "Usage: %s [ -s shell ] [ name ]\n"
- #define WHOAREYOU "%s: Cannot determine you user name.\n"
- #define UNKUSER "%s: Unknown user %s\n"
- #define NOPERM "You may not change the shell for %s.\n"
- #define NOPERM2 "can't change shell for `%s'\n"
- #define NEWSHELLMSG "Changing the login shell for %s\n"
- #define NEWSHELL "Login Shell"
- #define NEWSHELLMSG2 \
- "Enter the new value, or press return for the default\n\n"
- #define BADSHELL "%s is an invalid shell.\n"
- #define BADFIELD "%s: Invalid entry: %s\n"
- #define PWDBUSY "Cannot lock the password file; try again later.\n"
- #define PWDBUSY2 "can't lock /etc/passwd\n"
- #define OPNERROR "Cannot open the password file.\n"
- #define OPNERROR2 "can't open /etc/passwd\n"
- #define UPDERROR "Error updating the password entry.\n"
- #define UPDERROR2 "error updating passwd entry\n"
- #define DBMERROR "Error updating the DBM password entry.\n"
- #define DBMERROR2 "error updating DBM passwd entry.\n"
- #define NOTROOT "Cannot change ID to root.\n"
- #define NOTROOT2 "can't setuid(0).\n"
- #define CLSERROR "Cannot commit password file changes.\n"
- #define CLSERROR2 "can't rewrite /etc/passwd.\n"
- #define UNLKERROR "Cannot unlock the password file.\n"
- #define UNLKERROR2 "can't unlock /etc/passwd.\n"
- #define CHGSHELL "changed user `%s' shell to `%s'\n"
-
- /*
- * usage - print command line syntax and exit
- */
-
- void
- usage ()
- {
- fprintf (stderr, USAGE, Progname);
- exit (1);
- }
-
- /*
- * new_fields - change the user's login shell information interactively
- *
- * prompt the user for the login shell and change it according to the
- * response, or leave it alone if nothing was entered.
- */
-
- new_fields ()
- {
- printf (NEWSHELLMSG2);
- change_field (loginsh, NEWSHELL);
- }
-
- /*
- * check_shell - see if the user's login shell is listed in /etc/shells
- *
- * The /etc/shells file is read for valid names of login shells. If the
- * /etc/shells file does not exist the user cannot set any shell unless
- * they are root.
- */
-
- check_shell (shell)
- char *shell;
- {
- char buf[BUFSIZ];
- char *cp;
- int found = 0;
- FILE *fp;
-
- if (amroot)
- return 1;
-
- if ((fp = fopen ("/etc/shells", "r")) == (FILE *) 0)
- return 0;
-
- while (fgets (buf, BUFSIZ, fp) && ! found) {
- if (cp = strrchr (buf, '\n'))
- *cp = '\0';
-
- if (strcmp (buf, shell) == 0)
- found = 1;
- }
- fclose (fp);
-
- return found;
- }
-
- /*
- * restricted_shell - return true if the named shell begins with 'r' or 'R'
- *
- * If the first letter of the filename is 'r' or 'R', the shell is
- * considered to be restricted.
- */
-
- int
- restricted_shell (shell)
- char *shell;
- {
- char *cp;
-
- if (cp = strrchr (shell, '/'))
- cp++;
- else
- cp = shell;
-
- return *cp == 'r' || *cp == 'R';
- }
-
- /*
- * chsh - this command controls changes to the user's shell
- *
- * The only suppoerted option is -s which permits the
- * the login shell to be set from the command line.
- */
-
- int
- main (argc, argv)
- int argc;
- char **argv;
- {
- char user[BUFSIZ]; /* User name */
- int flag; /* Current command line flag */
- int sflg = 0; /* -s - set shell from command line */
- int i; /* Loop control variable */
- char *cp; /* Miscellaneous character pointer */
- struct passwd *pw; /* Password entry from /etc/passwd */
- struct passwd pwent; /* New password entry */
-
- /*
- * This command behaves different for root and non-root
- * users.
- */
-
- amroot = getuid () == 0;
- #ifdef NDBM
- pw_dbm_mode = O_RDWR;
- #endif
-
- /*
- * Get the program name. The program name is used as a
- * prefix to most error messages. It is also used as input
- * to the openlog() function for error logging.
- */
-
- if (Progname = strrchr (argv[0], '/'))
- Progname++;
- else
- Progname = argv[0];
-
- openlog (Progname, LOG_PID, LOG_AUTH);
-
- /*
- * There is only one option, but use getopt() anyway to
- * keep things consistent.
- */
-
- while ((flag = getopt (argc, argv, "s:")) != EOF) {
- switch (flag) {
- case 's':
- sflg++;
- strcpy (loginsh, optarg);
- break;
- default:
- usage ();
- }
- }
-
- /*
- * There should be only one remaining argument at most
- * and it should be the user's name.
- */
-
- if (argc > optind + 1)
- usage ();
-
- /*
- * Get the name of the user to check. It is either
- * the command line name, or the name getlogin()
- * returns.
- */
-
- if (optind < argc) {
- strncpy (user, argv[optind], sizeof user);
- pw = getpwnam (user);
- } else if (cp = getlogin ()) {
- strncpy (user, cp, sizeof user);
- pw = getpwnam (user);
- } else {
- fprintf (stderr, WHOAREYOU, Progname);
- exit (1);
- }
-
- /*
- * Make certain there was a password entry for the
- * user.
- */
-
- if (! pw) {
- fprintf (stderr, UNKUSER, Progname, user);
- exit (1);
- }
-
- /*
- * Non-privileged users are only allowed to change the
- * shell if the UID of the user matches the current
- * real UID.
- */
-
- if (! amroot && pw->pw_uid != getuid ()) {
- fprintf (stderr, NOPERM, user);
- syslog (LOG_WARN, NOPERM2, user);
- exit (1);
- }
-
- /*
- * Non-privileged users are only allowed to change the
- * shell if it is not a restricted one.
- */
-
- if (! amroot && restricted_shell (pw->pw_shell)) {
- fprintf (stderr, NOPERM, user);
- syslog (LOG_WARN, NOPERM2, user);
- exit (1);
- }
-
- /*
- * Make a copy of the user's password file entry so it
- * can be modified without worrying about it be modified
- * elsewhere.
- */
-
- pwent = *pw;
- pwent.pw_name = strdup (pw->pw_name);
- pwent.pw_passwd = strdup (pw->pw_passwd);
- #ifdef ATT_AGE
- pwent.pw_age = strdup (pw->pw_age);
- #endif
- #ifdef ATT_COMMENT
- pwent.pw_comment = strdup (pw->pw_comment);
- #endif
- pwent.pw_dir = strdup (pw->pw_dir);
- pwent.pw_gecos = strdup (pw->pw_gecos);
-
- /*
- * Now get the login shell. Either get it from the password
- * file, or use the value from the command line.
- */
-
- if (! sflg)
- strcpy (loginsh, pw->pw_shell);
-
- /*
- * If the login shell was not set on the command line,
- * let the user interactively change it.
- */
-
- if (! sflg) {
- printf (NEWSHELLMSG, user);
- new_fields ();
- }
-
- /*
- * Check all of the fields for valid information. The shell
- * field may not contain any illegal characters. Non-privileged
- * users are restricted to using the shells in /etc/shells.
- */
-
- if (valid_field (loginsh, ":,=")) {
- fprintf (stderr, BADFIELD, Progname, loginsh);
- exit (1);
- }
- if (! check_shell (loginsh)) {
- fprintf (stderr, BADSHELL, loginsh);
- exit (1);
- }
- pwent.pw_shell = loginsh;
- pw = &pwent;
-
- /*
- * Before going any further, raise the ulimit to prevent
- * colliding into a lowered ulimit, and set the real UID
- * to root to protect against unexpected signals. Any
- * keyboard signals are set to be ignored.
- */
-
- ulimit (2, 30000);
- if (setuid (0)) {
- fprintf (stderr, NOTROOT);
- syslog (LOG_ERR, NOTROOT2);
- exit (1);
- }
- signal (SIGHUP, SIG_IGN);
- signal (SIGINT, SIG_IGN);
- signal (SIGQUIT, SIG_IGN);
- #ifdef SIGTSTP
- signal (SIGTSTP, SIG_IGN);
- #endif
-
- /*
- * The passwd entry is now ready to be committed back to
- * the password file. Get a lock on the file and open it.
- */
-
- for (i = 0;i < 30;i++)
- if (pw_lock ())
- break;
-
- if (i == 30) {
- fprintf (stderr, PWDBUSY);
- syslog (LOG_WARN, PWDBUSY2);
- exit (1);
- }
- if (! pw_open (O_RDWR)) {
- fprintf (stderr, OPNERROR);
- syslog (LOG_ERR, OPNERROR2);
- (void) pw_unlock ();
- exit (1);
- }
-
- /*
- * Update the passwd file entry. If there is a DBM file,
- * update that entry as well.
- */
-
- if (! pw_update (pw)) {
- fprintf (stderr, UPDERROR);
- syslog (LOG_ERR, UPDERROR2);
- (void) pw_unlock ();
- exit (1);
- }
- #if defined(DBM) || defined(NDBM)
- if (access ("/etc/passwd.pag", 0) == 0 && ! pw_dbm_update (pw)) {
- fprintf (stderr, DBMERROR);
- syslog (LOG_ERR, DBMERROR2);
- (void) pw_unlock ();
- exit (1);
- }
- #endif
-
- /*
- * Changes have all been made, so commit them and unlock the
- * file.
- */
-
- if (! pw_close ()) {
- fprintf (stderr, CLSERROR);
- syslog (LOG_ERR, CLSERROR2);
- (void) pw_unlock ();
- exit (1);
- }
- if (! pw_unlock ()) {
- fprintf (stderr, UNLKERROR);
- syslog (LOG_ERR, UNLKERROR2);
- exit (1);
- }
- syslog (LOG_INFO, CHGSHELL, user, pwent.pw_shell);
- closelog ();
- exit (0);
- }
-