2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
5 * this program is free software. you can redistribute it and
6 * modify it under the terms of the gnu general public license.
7 * there is no warranty.
11 * $Date: 1998/06/11 22:30:14 $
13 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
14 * patches from Zefram <A.Main@dcs.warwick.ac.uk>
16 * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security
17 * suggestion from Zefram. Disallowing users with shells not in /etc/shells
18 * from changing their shell.
20 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org>
21 * - added Native Language Support
27 #define _POSIX_SOURCE 1
30 #include <sys/types.h>
45 #if REQUIRE_PASSWORD && USE_PAM
46 #include <security/pam_appl.h>
47 #include <security/pam_misc.h>
50 typedef unsigned char boolean
;
54 /* Only root is allowed to assign a luser a non-listed shell, by default */
55 #define ONLY_LISTED_SHELLS 1
60 static char buf
[FILENAME_MAX
];
67 static void parse_argv (int argc
, char *argv
[], struct sinfo
*pinfo
);
68 static void usage (FILE *fp
);
69 static char *prompt (char *question
, char *def_val
);
70 static int check_shell (char *shell
);
71 static boolean
get_shell_list (char *shell
);
72 static void *xmalloc (int bytes
);
74 #define memzero(ptr, size) memset((char *) ptr, 0, size)
77 main (int argc
, char *argv
[]) {
78 char *cp
, *shell
, *oldshell
;
82 #if REQUIRE_PASSWORD && USE_PAM
83 pam_handle_t
*pamh
= NULL
;
85 struct pam_conv conv
= { misc_conv
, NULL
};
89 setlocale(LC_ALL
, "");
90 bindtextdomain(PACKAGE
, LOCALEDIR
);
93 /* whoami is the program name for error messages */
95 if (! whoami
) whoami
= "chsh";
96 for (cp
= whoami
; *cp
; cp
++)
97 if (*cp
== '/') whoami
= cp
+ 1;
100 memzero (&info
, sizeof (info
));
102 parse_argv (argc
, argv
, &info
);
104 if (! info
.username
) {
107 fprintf (stderr
, _("%s: you (user %d) don't exist.\n"), whoami
, uid
);
111 pw
= getpwnam (info
.username
);
114 fprintf (stderr
, _("%s: user \"%s\" does not exist.\n"), whoami
, cp
);
118 if (!(is_local(pw
->pw_name
))) {
119 fprintf (stderr
, _("%s: can only change local entries; use yp%s instead.\n"),
124 oldshell
= pw
->pw_shell
;
125 if (!oldshell
[0]) oldshell
= "/bin/sh";
128 if (uid
!= 0 && (uid
!= pw
->pw_uid
|| !get_shell_list(oldshell
))) {
130 fprintf(stderr
,_("%s: Your shell is not in /etc/shells, shell change"
131 " denied\n"),whoami
);
137 printf( _("Changing shell for %s.\n"), pw
->pw_name
);
142 if (pam_start("chsh", pw
->pw_name
, &conv
, &pamh
)) {
143 puts(_("Password error."));
146 if (pam_authenticate(pamh
, 0)) {
147 puts(_("Password error."));
150 retcode
= pam_acct_mgmt(pamh
, 0);
151 if (retcode
== PAM_NEW_AUTHTOK_REQD
) {
152 retcode
= pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
153 } else if (retcode
) {
154 puts(_("Password error."));
157 if (pam_setcred(pamh
, 0)) {
158 puts(_("Password error."));
161 /* no need to establish a session; this isn't a session-oriented
165 /* require password, unless root */
166 if(uid
!= 0 && pw
->pw_passwd
&& pw
->pw_passwd
[0]) {
167 char *pwdstr
= getpass(_("Password: "));
168 if(strncmp(pw
->pw_passwd
,
169 crypt(pwdstr
, pw
->pw_passwd
), 13)) {
170 puts(_("Incorrect password."));
174 # endif /* USE_PAM */
175 #endif /* REQUIRE_PASSWORD */
178 shell
= prompt (_("New shell"), oldshell
);
179 if (! shell
) return 0;
182 if (check_shell (shell
) < 0) return (-1);
184 if (! strcmp (pw
->pw_shell
, shell
)) {
185 printf (_("Shell not changed.\n"));
188 if (!strcmp(shell
, "/bin/sh")) shell
= "";
189 pw
->pw_shell
= shell
;
190 if (setpwnam (pw
) < 0) {
192 printf( _("Shell *NOT* changed. Try again later.\n") );
195 printf (_("Shell changed.\n"));
201 * parse the command line arguments, and fill in "pinfo" with any
202 * information from the command line.
204 static void parse_argv (argc
, argv
, pinfo
)
211 static struct option long_options
[] = {
212 { "shell", required_argument
, 0, 's' },
213 { "list-shells", no_argument
, 0, 'l' },
214 { "help", no_argument
, 0, 'u' },
215 { "version", no_argument
, 0, 'v' },
216 { NULL
, no_argument
, 0, '0' },
221 c
= getopt_long (argc
, argv
, "s:luv", long_options
, &index
);
226 printf ("%s\n", util_linux_version
);
232 get_shell_list (NULL
);
239 pinfo
->shell
= optarg
;
246 /* done parsing arguments. check for a username. */
248 if (optind
+ 1 < argc
) {
252 pinfo
->username
= argv
[optind
];
258 * print out a usage message.
260 static void usage (fp
)
263 fprintf (fp
, _("Usage: %s [ -s shell ] "), whoami
);
264 fprintf (fp
, _("[ --list-shells ] [ --help ] [ --version ]\n"));
265 fprintf (fp
, _(" [ username ]\n"));
270 * ask the user for a given field and return it.
272 static char *prompt (question
, def_val
)
279 if (! def_val
) def_val
= "";
280 printf("%s [%s]: ", question
, def_val
);
282 if (fgets (buf
, sizeof (buf
), stdin
) == NULL
) {
283 printf (_("\nAborted.\n"));
286 /* remove the newline at the end of buf. */
288 while (isspace (*ans
)) ans
++;
290 while (len
> 0 && isspace (ans
[len
-1])) len
--;
291 if (len
<= 0) return NULL
;
293 cp
= (char *) xmalloc (len
+ 1);
299 * check_shell () -- if the shell is completely invalid, print
300 * an error and return (-1).
301 * if the shell is a bad idea, print a warning.
303 static int check_shell (shell
)
309 printf (_("%s: shell must be a full path name.\n"), whoami
);
312 if (access (shell
, F_OK
) < 0) {
313 printf (_("%s: \"%s\" does not exist.\n"), whoami
, shell
);
316 if (access (shell
, X_OK
) < 0) {
317 printf (_("%s: \"%s\" is not executable.\n"), whoami
, shell
);
320 /* keep /etc/passwd clean. */
321 for (i
= 0; i
< strlen (shell
); i
++) {
323 if (c
== ',' || c
== ':' || c
== '=' || c
== '"' || c
== '\n') {
324 printf (_("%s: '%c' is not allowed.\n"), whoami
, c
);
328 printf (_("%s: Control characters are not allowed.\n"), whoami
);
332 #if ONLY_LISTED_SHELLS
333 if (! get_shell_list (shell
)) {
335 printf (_("Warning: \"%s\" is not listed in /etc/shells\n"), shell
);
337 printf (_("%s: \"%s\" is not listed in /etc/shells.\n"),
339 printf( _("%s: use -l option to see list\n"), whoami
);
344 if (! get_shell_list (shell
)) {
345 printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell
);
346 printf( _("Use %s -l to see list.\n"), whoami
);
353 * get_shell_list () -- if the given shell appears in /etc/shells,
354 * return true. if not, return false.
355 * if the given shell is NULL, /etc/shells is outputted to stdout.
357 static boolean
get_shell_list (shell_name
)
365 fp
= fopen ("/etc/shells", "r");
367 if (! shell_name
) printf (_("No known shells.\n"));
370 while (fgets (buf
, sizeof (buf
), fp
) != NULL
) {
371 /* ignore comments */
372 if (*buf
== '#') continue;
374 /* strip the ending newline */
375 if (buf
[len
- 1] == '\n') buf
[len
- 1] = 0;
376 /* ignore lines that are too damn long */
378 /* check or output the shell */
380 if (! strcmp (shell_name
, buf
)) {
385 else printf ("%s\n", buf
);
392 * xmalloc () -- malloc that never fails.
394 static void *xmalloc (bytes
)
400 if (! vp
&& bytes
> 0) {
401 perror (_("malloc failed"));